diff --git a/.bazelrc b/.bazelrc index 8ba84ebeef..08f0561995 100644 --- a/.bazelrc +++ b/.bazelrc @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Bazel RC file for the GAPID build. +# Bazel RC file for the AGI build. # Import any user defined rules. try-import %workspace%/user.bazelrc @@ -22,28 +22,24 @@ build --verbose_failures test --test_output=errors query --nohost_deps --noimplicit_deps -# Adding in c++11 compiling, this really should be done in the android toolchains -build --cxxopt="-std=c++11" --host_cxxopt="-std=c++11" - # Android special build configurations build --fat_apk_cpu armeabi-v7a,arm64-v8a,x86 # Default version defines -build --define GAPID_BUILD_NUMBER=0 --define GAPID_BUILD_SHA=developer +build --define AGI_BUILD_NUMBER=0 --define AGI_BUILD_SHA=developer # Without this, bazel wraps each cc_library with "--whole-archive" flags for the # linker when building the dynamic library, which leads to over-bloated libs. build --incompatible_remove_legacy_whole_archive -# Makes bazel use Python 2 for genrules using Python executable targets. -build --host_force_python=PY2 - # Sets the go +build tags to use during compilation. build --define gotags=analytics,crashreporting # Disable c-ares for grpc. build --define grpc_no_ares=true +build --features=-debug_prefix_map_pwd_is_dot + # Config used by the build servers to dump symbols. build:symbols --copt="-g" @@ -52,3 +48,7 @@ build:nodroid --define NO_ANDROID=1 # Tests requiring a GPU fail in the sandbox. test --strategy TestRunner=standalone --test_env DISPLAY + +build:fuchsia_arm64 --crosstool_top=@fuchsia_clang//:toolchain --cpu=aarch64 --host_crosstool_top=@bazel_tools//tools/cpp:toolchain +build:fuchsia_arm64 --cxxopt="-Wno-extra-semi" +build:fuchsia_arm64 --define FUCHSIA_BUILD=1 --action_env=AGI_FUCHSIA_BUILD=1 diff --git a/.bazelversion b/.bazelversion index 227cea2156..91ff57278e 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -2.0.0 +5.2.0 diff --git a/.classpath b/.classpath index bfb8ca26b1..925b1bc99c 100644 --- a/.classpath +++ b/.classpath @@ -2,16 +2,28 @@ - + - + - - + + + + + + + + + + + + + + @@ -27,8 +39,10 @@ + + @@ -37,6 +51,7 @@ + diff --git a/.github/ISSUE_TEMPLATE/standard-bug-report-for-gapid.md b/.github/ISSUE_TEMPLATE/standard-bug-report-for-gapid.md index 5f97e5b6dc..179d0e904c 100644 --- a/.github/ISSUE_TEMPLATE/standard-bug-report-for-gapid.md +++ b/.github/ISSUE_TEMPLATE/standard-bug-report-for-gapid.md @@ -1,6 +1,6 @@ --- -name: Standard Bug report for GAPID -about: Create an actionable bug-report for GAPID +name: Standard Bug report for Android GPU Inspector +about: Create an actionable bug-report for Android GPU Inspector title: '' labels: '' assignees: '' @@ -8,7 +8,7 @@ assignees: '' --- **Environment information:** - - GAPID version: + - AGI version: - Host OS: [e.g. Linux] If tracing on Android: - Device model: @@ -27,5 +27,5 @@ Please paste a stacktrace here if it's available. If applicable, add screenshots to help explain your problem. **Additional debugging information** -- Please attach the generated _gapis.log_ and _gapic.log_ files you will find in the temp folder (e.g. /tmp/ on linux). -- If using Android: Please attach a full logcat dump (```adb logcat -d > logcat-full.txt```) that contains logs since GAPID was started. +- Please attach the generated _gapis.log_ and _gapic.log_ files you will find in the temp folder (e.g. /tmp/ on linux). +- If using Android: Please attach a full logcat dump (```adb logcat -d > logcat-full.txt```) that contains logs since AGI was started. diff --git a/.gitignore b/.gitignore index 010066a238..de6722d070 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,11 @@ # Directories -/.DS_Store /.idea -/.vscode +/**/.vscode /bazel-* /fused +/pkg +/**/__pycache__ +/**/.pytest_cache # Single files /.gapid-config @@ -14,10 +16,18 @@ # General patterns *.gfxtrace *.perfetto +*.fxt *ycm_extra_conf.py* gapir.log gapis.log +hs_err_pid*.log + +# MacOS +.DS_Store # Vim [._]*.s[a-w][a-z] [._]s[a-w][a-z] + +# Sublime +BUILD.sublime-workspace diff --git a/.project b/.project index 9957b3f503..b30774a8b3 100644 --- a/.project +++ b/.project @@ -63,10 +63,6 @@ org.eclipse.ui.ide.multiFilter 1.0-projectRelativePath-matches-false-false-bazel-external/androidsdk - - org.eclipse.ui.ide.multiFilter - 1.0-projectRelativePath-matches-false-false-bazel-external/llvm - diff --git a/BUILD.bazel b/BUILD.bazel index cecd17b31a..e435e19372 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -12,9 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -# Description: -# -# Gapid is a graphics API debugger. +# Root BUILD file for Android GPU Inspector. licenses(["notice"]) # Apache 2.0 @@ -24,12 +22,11 @@ load("@bazel_gazelle//:def.bzl", "gazelle") load("//tools/build:rules.bzl", "copy_to", "copy_tree") # gazelle:prefix github.com/google/gapid -# gazelle:resolve go go llvm/bindings/go/llvm @llvm//:go # gazelle:resolve go go protos/perfetto/common //tools/build/third_party/perfetto:common_go_proto # gazelle:resolve go go protos/perfetto/config //tools/build/third_party/perfetto:config_go_proto # gazelle:resolve go go protos/perfetto/ipc //tools/build/third_party/perfetto:ipc_go_proto -# gazelle:resolve proto proto protos/perfetto/config/perfetto_config.proto @perfetto//:protos_perfetto_config_merged_config_protos -# gazelle:resolve proto go protos/perfetto/config/perfetto_config.proto @gapid//tools/build/third_party/perfetto:config_go_proto +# gazelle:resolve proto proto protos/perfetto/config/trace_config.proto @perfetto//:protos_perfetto_config_protos +# gazelle:resolve proto go protos/perfetto/config/trace_config.proto @gapid//tools/build/third_party/perfetto:config_go_proto # gazelle:exclude gapic gazelle( @@ -43,8 +40,8 @@ gazelle( # Alias meant to be used with 'bazel run -- '. alias( - name = "gapid", - actual = "//cmd/gapid", + name = "agi", + actual = "//cmd/agi", ) alias( @@ -88,6 +85,7 @@ filegroup( "//tools/build:linux": [":perfetto"], "//tools/build:windows": [], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], }), ) @@ -138,7 +136,7 @@ copy_tree( srcs = [ "@vscode-languageclient//:vscode-languageclient", ], - strip = "external/vscode-languageclient/package", + strip = "package", to = "gfxapi-ls-0.0.1/node_modules/vscode-languageclient", ) @@ -147,7 +145,7 @@ copy_tree( srcs = [ "@vscode-jsonrpc//:vscode-jsonrpc", ], - strip = "external/vscode-jsonrpc/package", + strip = "package", to = "gfxapi-ls-0.0.1/node_modules/vscode-jsonrpc", ) @@ -156,14 +154,14 @@ copy_tree( srcs = [ "@vscode-languageserver-types//:vscode-languageserver-types", ], - strip = "external/vscode-languageserver-types/package", + strip = "package", to = "gfxapi-ls-0.0.1/node_modules/vscode-languageserver-types", ) copy_to( name = "pkg-root", srcs = [ - "//cmd/gapid", + "//cmd/agi", "//cmd/gapir/cc:gapir", "//cmd/gapis", "//cmd/gapit", @@ -197,16 +195,10 @@ copy_to( "@gapid//tools/build:linux": [ "//core/vulkan/vk_api_timing_layer/cc:json", "//core/vulkan/vk_api_timing_layer/cc:libVkLayer_CPUTiming", - ], - "//conditions:default": [ - ], - }) + select({ - "@gapid//tools/build:linux": [ "//core/vulkan/vk_memory_tracker_layer/cc:json", "//core/vulkan/vk_memory_tracker_layer/cc:libVkLayer_MemoryTracker", ], - "//conditions:default": [ - ], + "//conditions:default": [], }), rename = { "gapic_deploy.jar": "gapic.jar", @@ -240,7 +232,6 @@ test_suite( "//core/app/flags:go_default_test", "//core/assert:go_default_test", "//core/cc:tests", - "//core/codegen:go_default_test", "//core/context/keys:go_default_test", "//core/data:go_default_test", "//core/data/binary:go_default_test", @@ -258,8 +249,6 @@ test_suite( "//core/fault/stacktrace/crunch:go_default_test", "//core/git:go_default_test", "//core/image:go_default_test", - # "//core/java/jdbg:go_default_test", # disabled: flaky on Kokoro - "//core/java/jdwp:go_default_test", "//core/langsvr:go_default_test", "//core/log:go_default_test", "//core/math/f16:go_default_test", @@ -267,7 +256,6 @@ test_suite( "//core/math/f64:go_default_test", "//core/math/interval:go_default_test", "//core/math/sint:go_default_test", - "//core/memory/arena:go_default_test", "//core/memory/arena/cc:tests", "//core/memory_tracker/cc:tests", "//core/net:go_default_test", @@ -280,6 +268,7 @@ test_suite( "//core/os/device/remotessh:go_default_test", "//core/os/file:go_default_test", "//core/os/flock:go_default_test", + "//core/os/fuchsia/ffx:go_default_test", "//core/os/shell:go_default_test", "//core/os/shell/stub:go_default_test", "//core/stream:go_default_test", @@ -295,11 +284,7 @@ test_suite( tests = [ "//gapil/analysis:go_default_test", "//gapil/bapi:go_default_test", - "//gapil/compiler:go_default_test", - "//gapil/compiler/mangling/c:go_default_test", - "//gapil/compiler/mangling/ia64:go_default_test", - "//gapil/compiler/plugins/encoder/test:go_default_test", - "//gapil/compiler/plugins/replay:go_default_test", + "//gapil/encoder/test:encoder_test", "//gapil/fuzz:go_default_test", "//gapil/parser:go_default_test", "//gapil/resolver:go_default_test", @@ -314,7 +299,6 @@ test_suite( name = "tests-gapis-api", tests = [ "//gapis/api:go_default_test", - "//gapis/api/gles:go_default_test", "//gapis/api/test:go_default_test", "//gapis/api/transform:go_default_test", "//gapis/api/vulkan:go_default_test", @@ -328,7 +312,6 @@ test_suite( "//gapis/replay/builder:go_default_test", "//gapis/replay/scheduler:go_default_test", "//gapis/resolve:go_default_test", - "//gapis/resolve/dependencygraph:go_default_test", "//gapis/resolve/dependencygraph2:go_default_test", "//gapis/resolve/dependencygraph2/graph_visualization:go_default_test", ], @@ -359,10 +342,33 @@ test_suite( test_suite( name = "tests-general", tests = [ - "//test/integration/gles/replay:go_default_test", "//test/integration/replay:go_default_test", "//test/integration/service:go_default_test", - "//test/robot/stash/grpc:go_default_test", + ], +) + +test_suite( + name = "lint", + tests = [ + # __BEGIN_LINT + "//vulkan_generator:lint", + "//vulkan_generator/codegen_utils:lint", + "//vulkan_generator/handle_remapper:lint", + "//vulkan_generator/tests:lint", + "//vulkan_generator/vulkan_parser:lint", + "//vulkan_generator/vulkan_parser/api:lint", + "//vulkan_generator/vulkan_parser/internal:lint", + "//vulkan_generator/vulkan_parser/postprocess:lint", + # __END_LINT + ], +) + +test_suite( + name = "tests-vulkan-generator", + tests = [ + # Run the linting as part of Vulkan generator tests + "//:lint", # DO NOT REMOVE! + "//vulkan_generator/tests:test_vulkan_parsing", ], ) @@ -377,7 +383,6 @@ test_suite( "//core/app/flags:go_default_test", "//core/assert:go_default_test", "//core/cc:tests", - "//core/codegen:go_default_test", "//core/context/keys:go_default_test", "//core/data:go_default_test", "//core/data/binary:go_default_test", @@ -395,8 +400,6 @@ test_suite( "//core/fault/stacktrace/crunch:go_default_test", "//core/git:go_default_test", "//core/image:go_default_test", - "//core/java/jdbg:go_default_test", - "//core/java/jdwp:go_default_test", "//core/langsvr:go_default_test", "//core/log:go_default_test", "//core/math/f16:go_default_test", @@ -404,7 +407,6 @@ test_suite( "//core/math/f64:go_default_test", "//core/math/interval:go_default_test", "//core/math/sint:go_default_test", - "//core/memory/arena:go_default_test", "//core/memory/arena/cc:tests", "//core/memory_tracker/cc:tests", "//core/net:go_default_test", @@ -417,6 +419,7 @@ test_suite( "//core/os/device/remotessh:go_default_test", "//core/os/file:go_default_test", "//core/os/flock:go_default_test", + "//core/os/fuchsia/ffx:go_default_test", "//core/os/shell:go_default_test", "//core/os/shell/stub:go_default_test", "//core/stream:go_default_test", @@ -426,11 +429,7 @@ test_suite( "//core/text/parse:go_default_test", "//gapil/analysis:go_default_test", "//gapil/bapi:go_default_test", - "//gapil/compiler:go_default_test", - "//gapil/compiler/mangling/c:go_default_test", - "//gapil/compiler/mangling/ia64:go_default_test", - "//gapil/compiler/plugins/encoder/test:go_default_test", - "//gapil/compiler/plugins/replay:go_default_test", + "//gapil/encoder/test:encoder_test", "//gapil/fuzz:go_default_test", "//gapil/parser:go_default_test", "//gapil/resolver:go_default_test", @@ -440,7 +439,6 @@ test_suite( "//gapil/validate:go_default_test", "//gapir/cc:tests", "//gapis/api:go_default_test", - "//gapis/api/gles:go_default_test", "//gapis/api/test:go_default_test", "//gapis/api/transform:go_default_test", "//gapis/api/vulkan:go_default_test", @@ -450,7 +448,6 @@ test_suite( "//gapis/replay/builder:go_default_test", "//gapis/replay/scheduler:go_default_test", "//gapis/resolve:go_default_test", - "//gapis/resolve/dependencygraph:go_default_test", "//gapis/resolve/dependencygraph2:go_default_test", "//gapis/resolve/dependencygraph2/graph_visualization:go_default_test", "//gapis/service/box:go_default_test", @@ -460,10 +457,11 @@ test_suite( "//gapis/stringtable/minidown/scanner:go_default_test", "//gapis/stringtable/parser:go_default_test", "//gapis/vertex:go_default_test", - "//test/integration/gles/replay:go_default_test", + "//replay2/handle_remapper:handle_remapper_tests", + "//replay2/memory_remapper:memory_remapper_tests", "//test/integration/replay:go_default_test", "//test/integration/service:go_default_test", - "//test/robot/stash/grpc:go_default_test", + "//vulkan_generator/tests:test_vulkan_parsing", # __END_TESTS ], ) diff --git a/BUILDING.md b/BUILDING.md index f66b0ca94e..6e67680c75 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -1,19 +1,19 @@ -# Building GAPID +# Building Android GPU Inspector -GAPID uses the [Bazel build system](https://bazel.build/). The recommended version of Bazel is **1.2.0**. +AGI uses the [Bazel build system](https://bazel.build/). The recommended version of Bazel is **5.2.0**. -Bazel is able to fetch most of the dependencies required to build GAPID, but currently the Android SDK and NDK both need to be downloaded and installed by hand. +Bazel is able to fetch most of the dependencies required to build AGI, but currently the Android SDK and NDK both need to be downloaded and installed by hand. Please see the following OS specific guides for setting up the build environment. -After setting up the build environment, GAPID can be built in a terminal with: +After setting up the build environment, AGI can be built in a terminal with: ``` -cd +cd bazel build pkg ``` -The build output will be at `/bazel-bin/pkg`. +The build output will be at `/bazel-bin/pkg`. --- @@ -25,47 +25,59 @@ The build output will be at `/bazel-bin/pkg`. ### Install Bazel -In the console (with administrator privilege) type: +Start a console, with administrator privilege, and type: -`choco install bazel` +`choco install bazel --version 5.2.0` -Note: Installing bazel will also install MSYS into `C:\tools\msys64` and Python into `C:\tools\python27`. +In the same console, install Python and MSYS2 as well: + +`choco install python` +`choco install msys2` ### Install additional tools -Using the msys64 shell at `C:\tools\msys64\mingw64`: -1. Update MSYS with: `pacman -Syu`. +From the Start Menu select the `MSYS2 64bit / MSYS2 MinGW 64-bit` shell: +1. Update MSYS2 with: `pacman -Syu`. 2. If the update ends with “close the window and run it again”, close and reopen the window and repeat 1. 3. Fetch required tools with: `pacman -S curl git zip unzip patch` -4. Download gcc with: `curl -O http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-7.3.0-2-any.pkg.tar.xz` -5. Download gcc-libs with: `curl -O http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-libs-7.3.0-2-any.pkg.tar.xz` -6. Install gcc with: `pacman -U mingw-w64-x86_64-gcc*-7.3.0-2-any.pkg.tar.xz` -7. Close the MSYS terminal +4. Download gcc with: `curl -O http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-10.2.0-10-any.pkg.tar.zst` +5. Download gcc-libs with: `curl -O http://repo.msys2.org/mingw/x86_64/mingw-w64-x86_64-gcc-libs-10.2.0-10-any.pkg.tar.zst` +6. Install the downloaded packages with: `pacman -U mingw-w64-x86_64-gcc*-10.2.0-10-any.pkg.tar.zst` +7. Close the MSYS2 terminal -### Install Java Development Kit 8 +### Install Java Development Kit 11 -A JDK is required to build the user interface. If you do not already have a JDK installed, you can [install OpenJDK from here](https://adoptopenjdk.net) (good defaults are version 8 and Hotspot JVM). +A JDK is required to run a custom build of AGI. If you do not already have a suitable JDK installed, +you can [download the OpenJDK](http://jdk-mirror.storage.googleapis.com/index.html) we use on our +build bots. Make sure the `JAVA_HOME` environment variable points to the JDK. ### Install Android SDK and NDK -Unzip the [Android SDK](https://dl.google.com/android/repository/sdk-tools-windows-3859397.zip) to a directory of your choosing. - -To fetch the required packages, using a console type: - -``` -cd -tools\bin\sdkmanager.bat "platforms;android-26" "build-tools;29.0.2" ndk-bundle -``` - -Note: this will install the latest NDK in `\ndk-bundle`. The minimum required version of the NDK is r16b. - -If you do not have adb installed you can do so with: -``` -cd -tools\bin\sdkmanager.bat platform-tools -``` +If you have Android Studio installed, use it to install: +* SDK Platform: `Android 8.0 (Oreo) - API Level 26` +* SDK Tools: `NDK (Side by side) - 21.3.6528147` + +Otherwise you can use the basic Android command line tools: +1. Create a directory for the Android SDK, for example `C:\Android\sdk\`. +1. Create the subdirectories `\cmdline-tools\latest\`. This exact name allows the tools to determine the SDK directory. +1. Download the tools from [Android Studio downloads](https://developer.android.com/studio/#downloads). +1. Extract the tools somewhere and move the contents of `cmdline-tools\` to the created `\cmdline-tools\latest\`. +1. Use a console to fetch the required packages: + + ``` + cd + cmdline-tools\latest\bin\sdkmanager.bat "platforms;android-26" + cmdline-tools\latest\bin\sdkmanager.bat "build-tools;30.0.3" + cmdline-tools\latest\bin\sdkmanager.bat "ndk;21.3.6528147" + ``` + +1. If you do not have adb installed you can do so with: + ``` + cd + cmdline-tools\latest\bin\sdkmanager.bat platform-tools + ``` ### Configure the environment @@ -73,24 +85,20 @@ Either do this globally or in your shell every time. Make sure the environment is setup before you run bazel (`bazel shutdown` will shut it down). -1. Add `C:\tools\msys64\mingw64` to the PATH: - `set PATH=C:\tools\msys64\mingw64\bin;%PATH%` +1. Add MSYS2 binary directories to the PATH: + `set PATH=C:\tools\msys64\usr\bin;C:\tools\msys64\mingw64\bin;%PATH%` Running `where gcc` should now find mingw’s gcc. -1. Add `C:\tools\python27` to the PATH: - `set PATH=C:\tools\python27;%PATH%` - Alternatively, pass the path to python via the `--python_path` to bazel. See the [bazel documentation](https://docs.bazel.build/versions/master/windows.html#build-python) for more info. - 1. Set TMP to something very short. `C:\tmp` is known to work. For faster builds, add this folder to the excemptions of the Windows Defender anti-malware scanner. The following environment variables will need to be set prior to building: -| Variable | Target | -| ------------------- | ---------------------------------- | -| `ANDROID_HOME` | Path to Android SDK | -| `ANDROID_NDK_HOME` | Path to Android NDK | -| `BAZEL_SH` | `C:\tools\msys64\usr\bin\bash.exe` | -| `TMP` | `C:\tmp` | +| Variable | Target | +| ------------------- | ----------------------------------------------------------- | +| `ANDROID_HOME` | Path to Android SDK, e.g. `C:\Android\sdk` | +| `ANDROID_NDK_HOME` | Path to Android NDK, e.g. `%ANDROID_HOME%\ndk\21.3.6528147` | +| `BAZEL_SH` | `C:\tools\msys64\usr\bin\bash.exe` | +| `TMP` | `C:\tmp` | --- @@ -100,12 +108,18 @@ The following environment variables will need to be set prior to building: Follow the [MacOS Bazel Install](https://docs.bazel.build/versions/master/install-os-x.html) directions to install bazel. -### Install Java Development Kit 8 +### Install Java Development Kit 11 -A JDK is required to build the user interface. If you do not already have a JDK installed, you can [install OpenJDK from here](https://adoptopenjdk.net) (good defaults are version 8 and Hotspot JVM). +A JDK is required to run a custom build of AGI. If you do not already have a suitable JDK installed, +you can [download the OpenJDK](http://jdk-mirror.storage.googleapis.com/index.html) we use on our +build bots. Make sure the `JAVA_HOME` environment variable points to the JDK. +> :warning: If you find the application menu bar non-responsive when launching your build of AGI, +> the following command should fix it: +> `sudo mkdir $JAVA_HOME/bin/Contents` + ### Install Android SDK and NDK Unzip the [Android SDK](https://dl.google.com/android/repository/sdk-tools-darwin-3859397.zip) to a directory of your choosing. @@ -114,18 +128,24 @@ To fetch the required packages, using a console type: ``` cd -tools/bin/sdkmanager "platforms;android-26" "build-tools;29.0.2" ndk-bundle +tools/bin/sdkmanager "platforms;android-26" "build-tools;30.0.3" ``` -Note: this will install the latest NDK in `/ndk-bundle`. The minimum required version of the NDK is r16b. - If you do not have adb installed you can do so with: ``` cd tools/bin/sdkmanager platform-tools ``` -### Install the XCode command line tools +Install +[Android NDK **r21d**](https://dl.google.com/android/repository/android-ndk-r21d-darwin-x86_64.dmg) (installing the App Bundle is needed in order to keep the notarization) +into the /Applications/ folder, and set the `ANDROID_NDK_HOME` environment pointing to NDK subdirectory: + +``` +export ANDROID_NDK_HOME=/Applications/AndroidNDK6528147.app/Contents/NDK +``` + +### Install the XCode 12 command line tools After installing, ensure the XCode license is signed with: @@ -134,6 +154,16 @@ sudo xcode-select -s /Applications/Xcode.app/Contents/Developer sudo xcodebuild -license ``` +### Install Clang-12 + +We use Clang-12 to build AGI on Linux which should be part of XCode 12. If there is no clang on the system it and can be downloaded from https://apt.llvm.org/. After downloading and installing Clang-12, add it to environment. + +``` +export CC=clang-12 +``` + +Alternatively, GCC can also be used for compiling AGI but we cannot guarantee that every GCC version will be able to compile AGI. + ### Increase the maximum number of OS file handles Bazel can concurrently use more file handles than the OS supports by default. This can be easily fixed by typing: @@ -167,9 +197,22 @@ Follow the [Ubuntu Bazel Install](https://docs.bazel.build/versions/master/insta Alternatively, bazel can be downloaded from its [GitHub Releases Page](https://github.com/bazelbuild/bazel/releases). -### Install Java Development Kit 8 +### Install Clang-12 + +We use Clang-12 to build AGI on Linux which can be downloaded from https://apt.llvm.org/ After downloading and installing Clang-12, +add it to environment. + +``` +export CC=clang-12 +``` + +Alternatively, GCC can also be used for compiling AGI but we cannot guarantee that every GCC version will be able to compile AGI. -A JDK is required to build the user interface. If you do not already have a JDK installed, you can [install OpenJDK from here](https://adoptopenjdk.net) (good defaults are version 8 and Hotspot JVM). +### Install Java Development Kit 11 + +A JDK is required to run a custom build of AGI. If you do not already have a suitable JDK installed, +you can [download the OpenJDK](http://jdk-mirror.storage.googleapis.com/index.html) we use on our +build bots. Make sure the `JAVA_HOME` environment variable points to the JDK. @@ -181,17 +224,24 @@ To fetch the required packages, using a console type: ``` cd -tools/bin/sdkmanager "platforms;android-26" "build-tools;29.0.2" ndk-bundle +tools/bin/sdkmanager "platforms;android-26" "build-tools;30.0.3" ``` -Note: this will install the latest NDK in `/ndk-bundle`. The minimum required version of the NDK is r16b. - If you do not have adb installed you can do so with: ``` cd tools/bin/sdkmanager platform-tools ``` +Unzip the +[Android NDK **r21d**](https://dl.google.com/android/repository/android-ndk-r21d-linux-x86_64.zip) +into a directory of your choosing, and set the `ANDROID_NDK_HOME` environment +variable to point to this directory: + +``` +export ANDROID_NDK_HOME= +``` + ### Install other libraries ``` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1e82b5f282..ed0ea9c961 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,32 +5,32 @@ just a few small guidelines you need to follow. ## Check out the source -Do the following to check out the GAPID source code on GitHub: +Do the following to check out the Android GPU Inspector source code on GitHub: 1. Sign up for GitHub at https://github.com/ if you don’t already have an account. 1. (Optional) Set up an SSH key to [connect to your account using SSH]. 1. (Optional) [Add a GPG signing key to your account]. -1. Go to the project landing page at https://github.com/google/gapid. +1. Go to the project landing page at https://github.com/google/agi. 1. [Fork the repository]. This creates a copy of the repository in your account. 1. Create a work folder on your workstation. The rest of this document assumes `~/work`, adjust as needed. -1. On _your_ GAPID project page, [clone your copy of the repository] and add it to your local work folder: +1. On _your_ AGI project page, [clone your copy of the repository] and add it to your local work folder: ``` cd ~/work git clone - cd gapid + cd agi ``` 1. Add the Google remote repository to your local repository: ``` - git remote add goog git@github.com:google/gapid.git + git remote add goog git@github.com:google/agi.git git fetch goog ``` ## (Optional) Configure git -Use the following commands to configure git for GAPID development: +Use the following commands to configure git for AGI development: ``` # Assume the remote branch has the same name as your local branch to make pushing changes easier -git config push.default current +git config push.default current # Default to pushing to your fork (assuming the above directions) git config remote.pushDefault origin # Make git clean up all the remote tags it creates when you delete remote branches @@ -38,13 +38,13 @@ git config fetch.prune true git config user.name # Add --global to make this a global setting git config user.email # Can also be a global setting # If you added a GPG signing key, run the following commands: -git config user.signingkey +git config user.signingkey git config commit.gpgsign true ``` -## Build GAPID for the first time +## Build Android GPU Inspector for the first time -Follow the [build instructions] in the GAPID repository. +Follow the [build instructions] in the AGI repository. ## Sign the Contributor License Agreement @@ -58,19 +58,24 @@ You generally only need to submit a CLA once, so if you've already submitted one ## Open a pull request (PR) -Do the following to contribute to the GAPID project: +Do the following to contribute to the AGI project: 1. Prepare your changes on a dedicated branch in your local repository: ``` git checkout -b - ``` -1. Make changes, commit the changes, and squash them into a single commit. -1. Use the presubmit script to check code formatting and other things: ``` - # Install clang-format 6.0 - sudo apt-get install -y clang-format-6.0 +1. Make changes, commit the changes, and squash them into a single commit. +1. Make sure you've setup your environment correctly before running the presubmit tests: + ``` + # Install clang-format 6.0 + sudo apt-get install -y clang-format-6.0 export CLANG_FORMAT=clang-format-6.0 - # Run the script + + # Provide path for the autopep8 binary (or you can add its location to your PATH) + export AUTOPEP8=~/.local/bin/autopep8 + ``` +1. Run the presubmit script to check code formatting and other things: + ``` ./kokoro/presubmit/presubmit.sh ``` 1. Fix potential issues, commit the fix, squash into a single commit again. @@ -79,12 +84,18 @@ Do the following to contribute to the GAPID project: ``` bazel test tests ``` + To skip the tests that require a GPU/device connected, run: + + ``` + bazel test tests --test_tag_filters=-needs_gpu + ``` + 1. Push to your GitHub repo: ``` - git push + git push ``` -1. Visit https://github.com/google/gapid to see a pop-up dialog inviting you to open a PR; click on the dialog to create a PR. See [Creating a pull request from a fork] for more information. -1. All submissions, including submissions by project members, require review. You can request specific reviewers for your PR or leave the reviewers section blank. A GAPID team member will review the request. +1. Visit https://github.com/google/agi to see a pop-up dialog inviting you to open a PR; click on the dialog to create a PR. See [Creating a pull request from a fork] for more information. +1. All submissions, including submissions by project members, require review. You can request specific reviewers for your PR or leave the reviewers section blank. An AGI team member will review the request. Consult [GitHub Help] for more information on using pull requests. @@ -92,6 +103,6 @@ Consult [GitHub Help] for more information on using pull requests. [Add a GPG signing key to your account]: https://help.github.com/en/articles/adding-a-new-gpg-key-to-your-github-account [Fork the repository]: https://help.github.com/en/articles/fork-a-repo [clone your copy of the repository]: https://help.github.com/en/articles/cloning-a-repository -[build instructions]: https://github.com/google/gapid/blob/master/BUILDING.md +[build instructions]: https://github.com/google/agi/blob/master/BUILDING.md [Creating a pull request from a fork]: https://help.github.com/en/articles/creating-a-pull-request-from-a-fork -[GitHub Help]: https://help.github.com/articles/about-pull-requests/ \ No newline at end of file +[GitHub Help]: https://help.github.com/articles/about-pull-requests/ diff --git a/DEVDOC.md b/DEVDOC.md index 1d60928715..11bf3ca4a0 100644 --- a/DEVDOC.md +++ b/DEVDOC.md @@ -1,37 +1,77 @@ -# GAPID Developer Documentation +# Android GPU Inspector Developer Documentation -## Setup Golang development +See [vulkan_generator/DEVDOC.md](vulkan_generator/DEVDOC.md) vulkan_generator specific build instructions. -This project contains Golang code, but it does not have the file hierarchy of -regular Golang projects (this is due to the use of Bazel as a build system). +## Build instructions + +See [BUILDING.md](BUILDING.md). + +## Setup to run presubmit tests locally + +Before creating a pull-request, check that your code can compile and that the +presubmit tests pass. + +To be able to run the presubmit tests locally, install the following: + +``` +# Buildifier +go get github.com/bazelbuild/buildtools/buildifier + +# Buildozer +go get github.com/bazelbuild/buildtools/buildozer + +# Clang format 6.0 +## On Debian-based Linux (see https://releases.llvm.org/download.html for binaries) +apt-get install clang-format-6.0 +## Make sure to set the CLANG_FORMAT environment variable, e.g. in bash: +export CLANG_FORMAT=clang-format-6.0 +``` + +With the above setup, you can run presubmit tests locally with: + +``` +./kokoro/presubmit/presubmit.sh +``` + +## Setup Go development + +This project contains Go code, but it does not have the file hierarchy of +regular Go projects (this is due to the use of Bazel as a build system). The `cmd/gofuse` utility enables to re-create the file hierarchy expected by Go -tools: +tools. Note, however, `cmd/gofuse` is not supported on Windows, but it is not +required to build/develop AGI either. The steps described here are optional and +are only intended to facilitate working on the AGI codebase using an IDE or +other Go tooling. ```sh # Make sure to build to have all compile-time generated files -cd +cd bazel build pkg -# Prepare a gapid-gofuse directory **outside of the gapid checkout directory** -mkdir /gapid-gofuse +# Prepare a agi-gofuse directory **outside of the AGI checkout directory** +mkdir /agi-gofuse # Run gofuse with the previous directory as a target -bazel run //cmd/gofuse -- -dir +bazel run //cmd/gofuse -- -dir + +# If the previous command fails to correctly guess the sub-directory under `bazel-out`, +# please pass it with the command below. For example if you build on Windows, the +# standard is `x64_windows-fastbuild`. If you build on Linux with +# `bazel build -c dbg pkg`, the is `k8-dbg`. +bazel run //cmd/gofuse -- -dir -bazelout + +# Build the package again to output the original compile-time generated files again. +bazel build pkg -# Add gapid-gofuse directory to your GOPATH environment variable. +# Add agi-gofuse directory to your GOPATH environment variable. # On Linux, with a bash shell, you can add the following to your ~/.bashrc file: -export GOPATH="${GOPATH:+${GOPATH}:}" +export GOPATH="${GOPATH:+${GOPATH}:}" # On other configurations, please search online how to add/edit environment variables. ``` -If you encounter a symlink error on Windows like 'a required privilege is not held by the client', -you have to use a command prompt with administrator privileges or enable -[Developer Mode](https://docs.microsoft.com/en-us/windows/uwp/get-started/enable-your-device-for-development) -as described [here](https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/). - After adding the gofuse directory to your GOPATH, Go tools should work as expected. You can edit files under the newly populated gofuse directory. You -should still compile under the original checkout directory of GAPID. +should still compile under the original AGI checkout directory. > Despite its name, the gofuse command does NOT use FUSE (filesystem in userspace). > It just creates directories and links to source files, including generated files. @@ -41,15 +81,20 @@ should still compile under the original checkout directory of GAPID. In terms of editor, [VsCode](https://code.visualstudio.com/) has good Go support thanks to its [Go extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.Go). -With the GOPATH setup to gofuse and opening the `` directory, +With the GOPATH setup to gofuse and opening the `` directory, as the root of your workspace, you should get some jump-to-definition and autocomplete features working. Make sure to edit the files through their link found under the gofuse directory. -## How to debug / breakpoint in Golang code +## How to debug / breakpoint in Go code + +The recommended Go debugger is +[delve](https://github.com/go-delve/delve). You can start a **debug** build of +gapis or a client under this debugger. To build in debug mode, use the `-c dbg` +Bazel flag, e.g.: -The recommended Golang debugger is -[delve](https://github.com/go-delve/delve). You can start a debug build of gapis -or a client under this debugger. +``` +bazel build -c dbg pkg +``` ### Debugging GAPIS @@ -86,6 +131,28 @@ If you want to debug a client like gapit, just start it under dlv: dlv exec ./bazel-bin/pkg/gapit ``` +### Debugging a test + +You can build a test in debug mode and then start it under dlv. + +For instance: + +``` +bazel test --cache_test_results=no -c dbg //core/data/slice:go_default_test +``` + +In practice, on Linux this builds and runs a test executable that is located at +`./bazel-out/k8-dbg/bin/core/data/slice/linux_amd64_debug/go_default_test`, +you will have to adapt the `k8-dbg` part will be different on other platforms. +To debug a specific test, you can start this executable under dlv: + +``` +$ dlv exec ./bazel-out/k8-dbg/bin/core/data/slice/linux_amd64_debug/go_default_test -- -test.run TestReplace +Type 'help' for list of commands. +(dlv) break TestReplace +Breakpoint 1 set at 0x5f5d4b for github.com/google/gapid/core/data/slice_test.TestReplace() core/data/slice/slice_test.go:26 +``` + ### Use a Delve init script To automate a delve startup sequence, you can edit a script of delve commands to @@ -112,7 +179,7 @@ dlv exec --init my-delve-init-script.txt ### Integration with an IDE If you want to interact with the debugger via your editor or IDE, be aware that -delve will think file paths start from the gapid top directory, and not your +delve will think file paths start from the AGI top directory, and not your root directory. This is very likely due to Bazel compilation. You may have to find workarounds if you call delve from an editor/IDE which consider the file paths to start from another directory, typically your root directory. There may @@ -123,13 +190,21 @@ See the workaround for VSCode below, any help to fix it for other IDEs is very w #### Integration with VSCode and Delve -To use the delve debugger for Go with VSCode to debug `gapis`. These steps can be followed: +Follow these steps to use the delve debugger for Go with VSCode to debug `gapis`. + +1. Make sure to complete the Go setup above for AGI. + +2. Settings file: There are two settings file(`settings.json`) that can be written. + - Global one that applies to all projects that can be opened with `Ctrl + Shift + P` and `Preferences: Open Settings (JSON)`. + Add this line to ensure that you have a stable tools directory: + `"go.toolsGopath": "",` -1. Make sure to complete Golang Setup for GAPID. + - Local one is under `.vscode` folder in your project folder. Create one if it does not already exist and add this line to your local settings to be able to search source code in AGI: + `"go.gopath": ""`, -2. Create a `launch.json` under the workspace directory with `Ctrl + Shift + P` and `Debug: Open launch.json` +3. Launch file: Create a `launch.json` file under the workspace directory with `Ctrl + Shift + P` and `Debug: Open launch.json` -3. Paste this as one of the launch configurations. This will ensure that there is a launch configuration for attaching to Delve. +4. Paste the following as one of the launch configurations. This will ensure that there is a launch configuration for attaching to Delve. ``` { ... @@ -141,8 +216,8 @@ To use the delve debugger for Go with VSCode to debug `gapis`. These steps can b "request": "attach", "mode": "remote", "apiVersion": 2, - "remotePath": "gapis/", - "cwd": "${workspaceFolder}/src/github.com/google/gapid/gapis", + "remotePath": "", + "cwd": "``", "dlvLoadConfig": { "followPointers": true, "maxVariableRecurse": 1, @@ -159,7 +234,7 @@ To use the delve debugger for Go with VSCode to debug `gapis`. These steps can b ``` As an example, `` could be `127.0.0.1` and `` could be `1234`. -4. Start delve in headless mode at gapid check-in folder. +5. Start delve in headless mode in the AGI root folder. ``` dlv exec --headless --listen=: --api-version 2 ./bazel-bin/pkg/gapis -- ``` @@ -169,15 +244,17 @@ The command below will allow using port `1234` (or any other preferred port) to dlv exec --headless --listen=127.0.0.1:1234 --api-version 2 ./bazel-bin/pkg/gapis -- -persist -rpc localhost:8888 ``` -5. Start debugging with `Debug->Start Debugging` (on Linux with `F5`) and make sure `Attach to Delve` is selected as the launch configuration. +6. Start debugging with `Debug->Start Debugging` (on Linux with `F5`) and make sure `Attach to Delve` is selected as the launch configuration. -6. Now VSCode can interact with Delve and can be used for debugging `gapis` in VSCode UI instead of command line. Enjoy your debugging :) +7. Now VSCode can interact with Delve and can be used for debugging `gapis` in VSCode UI instead of the command line. Enjoy your debugging :) + +This allows you to put breakpoint at any line in AGI Go source code regardless if they are handwritten, generated or in Go Standard Library. For the generated file, you can put the breakpoints under the `bazel-bin/` or `bazel-out/` folder and the debugger will still find it under the fuse directory during debugging and open it. The only downside is you will have two versions of the same file but this is the only working workaround until Go Plugin supports bazel-generated files. ## How to debug via printing message You can use the built-in logging functions to place debug prints. -In Golang: +In Go: ```go import ( @@ -209,3 +286,475 @@ $ ./bazel-bin/pkg/gapit -fullhelp The Error level is recommended when adding debug print, to make sure it is not filtered away. + +## How to debug a replay crash + +The replayer uses [breakpad](https://chromium.googlesource.com/breakpad/breakpad) to catch and optionnaly report crashes. + +If you want to analyze a replay crash with a debugger, on **64 bits Android** (for other platforms, adapt as necessary): + +1. Start the replayer with the `--wait-for-debugger` flag, e.g. `./gapit screenshot --gapir-args '--wait-for-debugger' mytrace.gfxtrace`. + +2. Wait for the replayer to launch on the device. + +3. Attach your debugger to the replayer app (`com.google.android.gapid.arm64v8a/com.google.android.gapid.ReplayerActivity`). + +4. Once attached, you probably want to add a breakpoint at `CrashHandler::handleMinidump` in order to break upon a crash, before it is reported to the server. + +5. When you attach, the replayer is spin-waiting in the loop defined in `core/cc/android/debugger.cpp`. To break this loop, use the debugger to set `gIsDebuggerAttached = true` before continuing execution. + +Note that while you attach the debugger and setup the breakpoint, the server might timeout waiting for a gRPC connection. You may increase this timeout by editing the `gapir/client:gRPCConnectTimeout` constant. + +## GAPIS build-time options to help with debugging + +See `gapis/config/config.go` for a list of various build-time config options +that can help with debugging. + +## How to profile AGI internals + +### GAPIS + +The server has instrumentation to output profiling information: + +``` +$ ./agi/gapis --fullhelp +[...] + -profile-pprof + enable pprof profiling + -profile-trace string + write a trace to file +[...] +``` + +The `-profile-trace` option generates a trace that can be open in Chrome via +`chrome://tracing`, or using [Perfetto web UI](https://ui.perfetto.dev/). + +The `gapit` and `agi` (UI starter) clients can pass these arguments to gapis via +`-gapis-args`, e.g.: + +``` +# GAPIT +./agi/gapit -gapis-args '-profile-trace my-profile-trace.out' + +# GAPIC +./agi/agi -gapis-args '-profile-trace my-profile-trace.out' foobar.gfxtrace +``` + +### On-device: GAPII, GAPIR + +To profile the interceptor GAPII and the replayer GAPIR on Android devices, you +can resort to classic profiling solutions. Check the Android [system tracing +overview](https://developer.android.com/topic/performance/tracing) doc. Beside +systrace, perfetto and the Android studio profile, also note that +[Inferno](https://android.googlesource.com/platform/system/extras/+/master/simpleperf/doc/inferno.md) +makes it easy to get flamegraphs. + +To profile the replayer using AGI itself, you can export a given replay into a +standalone APK (using `gapit export_replay -apk`), and profile that replay-APK +using AGI. + +## Unit tests + +Unit testing is achieved with separate frameworks depending on the programming +language, but they are all accessible with Bazel. + +### List tests + +```sh +# List all tests +bazel query 'tests(//...)' + +# List all Go tests +bazel query 'kind(go_.*, tests(//...))' +# List all C++ tests +bazel query 'kind(cc_.*, tests(//...))' +``` + +### Run tests + +```sh +# Run all the tests +bazel test tests + +# Run a given test +bazel test //core/log:go_default_test +``` + +### Go + +Following the [regular Go test](https://golang.org/doc/code.html#Testing) setup, +tests are written as `func TestXXX(t *testing.T)` functions in `*_test.go` +files. + +Adding a Go test file into Bazel is done by invoking Gazelle. The +`kokoro/presubmit/presubmit.sh` script does that for you: if you create or +remove `*_test.go` files, running the presubmit script automatically edit the +BUILD.bazel files to reflect your changes. + +A few useful homemade packages: + +- `github.com/google/gapid/core/assert` defines an assertion framework. + +- `github.com/google/gapid/core/log` lets you create contexts for tests with + `ctx := log.Testing(t)` + +### C++ + +> TODO + +### Java + +> TODO + +## Code generated at compile time + +A large amount of code is generated at compile time. Code is typically generated +via APIC (API Compiler, `cmd/apic/`). APIC takes as input `.api` and `.tmpl` +files, and it generates code in another language. + +``` + + +----------+ + <.api files> --->| | + | APIC |---> + <.tmpl files> --->| | + +----------+ + +``` + +`.api` files are GAPIL ("graphics API language") sources. GAPIL is a +domain-specific language to specify a graphics API, it is documented in +`gapil/README.md`. AGI currently supports only Vulkan, but its ancestor GAPID +was designed to support arbitrary graphics APIs. Vulkan is described by API files +located under `gapis/api/vulkan/`, the top-level file is +`gapis/api/vulkan/vulkan.api`. + +`.tmpl` files are template files. The templating language is documented in +`gapis/api/templates/README.md`. There are various template files in the project +to generate code for the interceptor, server, replayer, Vulkan layers, etc. + +APIC is a GAPIL compiler, its entry-point is defined in `cmd/apic/` and the +actual compiler logic is defined in `gapil/`. In a nutshell, APIC parses the +`.api` files to gather information about the graphics API, and then instantiates +the templates in the `.tmpl` files to generate code. + +### Where does the generated code end up? + +The generated code is not checked under version control. It is generated at +compilation time by Bazel rules calling APIC, resulting in files that can be +seen under e.g. `bazel-bin/`. + +For instance, generated C++ code for the interceptor can be found in: + +``` +user@machine:~/work/agi$ find -L bazel-bin -name '*_spy_*.cpp' +bazel-bin/gapii/cc/vulkan_spy_0.cpp +bazel-bin/gapii/cc/vulkan_spy_subroutines_1.cpp +bazel-bin/gapii/cc/vulkan_spy_subroutines_0.cpp +bazel-bin/gapii/cc/vulkan_spy_2.cpp +bazel-bin/gapii/cc/vulkan_spy_3.cpp +bazel-bin/gapii/cc/vulkan_spy_helpers.cpp +bazel-bin/gapii/cc/vulkan_spy_1.cpp +``` + +To get a unified file tree view, interleaving source files with generated files, +you can use the `cmd/gofuse` setup. `cmd/gofuse` links the generated source +files in the same directories as their siblings in the same packages/namespaces. + +------------------------------------------------------------------------------- + +## Life of a gfxtrace + +This is an overview of how AGI captures graphics API calls, stores them into a +gfxtrace file, and process that file to replay the calls. + +This is oriented to AGI developers who need to work with AGI internals. It is +meant to help build a high-level mental model that facilitates navigating the +actual source code. It references concepts and code modules that are unlikely to +change anytime soon, and it tries not to reference details that are likely to +change. You are encouraged to check the source code while reading this, to +clarify how the various parts actually fit together. If you spot any +discrepancy, please update this doc, but please keep it high-level. + +AGI is architectured to be API-agnostic in order to support multiple graphics +APIs. GAPID, from which AGI was forked, supports GLES and Vulkan. AGI's main +focus is Vulkan on Android, so all examples in this doc are related to Vulkan on +Android. + +### AGI's overall architecture + +AGI is separated in main components that interact mostly via protobuf: + +- GAPIS (Graphics API Server, Go code under `gapis/`, see `gapis/README.md`) is + the main component, running on the developer desktop/laptop. Any complex logic + is meant to be implemented in GAPIS, while the other components are meant to + be kept as simple as possible. The protobuf interface of GAPIS is defined in + `gapis/service/service.proto`. + +- GAPII (Graphics API Interceptor, mostly C++ code under `gapii/`, see + `gapii/README.md`) is the component responsible for intercepting graphics API + calls during capture. It interacts with GAPIS via a dedicated protocol. + +- GAPIR (Graphics API Replayer, mostly C++ code under `gapir/`, see + `gapir/README.md`) is the component that can replay graphics API calls. Its + protobuf interface is defined in `gapir/replay_service/service.proto`. + +- GAPIC (Graphics API Client, Java code under `gapic/`, see `gapic/README.md`) + is the GUI client. It uses GAPIS protobuf interface. + +- GAPIT (Graphics API Terminal, Go code under `cmd/gapit/`) is the + developer-oriented CLI client. It uses GAPIS protobuf interface. + +When you start AGI via the desktop icon or the `./agi` command, this triggers +the entry point defined under `cmd/agi/`, which by default starts a new GAPIS +and then a new GAPIC that connects to this GAPIS. GAPIS may then itself start +GAPII or GAPIR instances. + +To capture and replay on Android, AGI has APKs (one per ABI, e.g. +`gapid-arm64-v8a.apk`) that embeds GAPII and GAPIR. + +### Create a gfxtrace: capture, serialize and store Vulkan calls + +AGI uses a Vulkan layer to intercept and capture the Vulkan calls emitted by an +application, and stores them into a gfxtrace file. Be aware that in the code +base, the act of intercepting graphics API calls may be referred by the words +"capture", "trace", "intercept" and "spy". + +For a Vulkan capture on Android, the main steps are: + +1. GAPIS issues a few adb commands to edit global settings that tell the Android + Vulkan loader to insert AGI's Vulkan layer when starting the app to capture. + This layer is called `GraphicsSpy` and is implemented by + `libVkLayer_GraphicsSpy.so` which is mostly a wrapper around `libgapii.so`. + +2. The capture layer and GAPIS establish a TCP connection over ADB. The + `gapii::Spy::Spy()` creator contains the logic to establish this connection. + +3. The capture layer monitors every Vulkan API call, and shadows the Vulkan + state accordingly. The logic of each Vulkan command is implemented in `.api` + files under `gapis/api/vulkan/`, and code is auto-generated from these files + (see [Code generated at compile time](#code-generated-at-compile-time)). In + particular, each command has a `mutate` function that mutates (updates) the + Vulkan state with respect to the command logic. For instance, + `vkCreateBuffer()` mutation results in adding a new buffer in the Vulkan + state. + +4. When the user clicks "Start" to start the one-frame capture, GAPIS tells the + capture layer that it wants the next frame to be captured. The capture layer + waits for the current frame to end before streaming the whole Vulkan state + back to GAPIS (see e.g. `VulkanSpy::serializeGPUBuffers`). After that, each + new intercepted Vulkan command and related memory observations are streamed + to GAPIS. When the frame terminates, the capture layer sends a special "end" + message to GAPIS. + +5. GAPIS receives the serialized data from the capture layer and stores it into + a `gfxtrace` file, until it receives the special "end" message. + +### What's in a gfxtrace file? + +A `gfxtrace` file contains data encoded in +[proto-pack](https://github.com/google/agi/tree/master/core/data/pack/README.md), +a homemade format to encapsulate protobuf messages. + +To see the plain content of a gfxtrace, you can use the `./gapit unpack -verbose +myfile.gfxtrace` command. On a simple Vulkan app, this produces the following +(here edited to fit, and with added `#` comments): + +``` +# Header +Object(msg: Headerᵈ{ABI: ABIᵈ{OS: 4, architecture: 1, ...}) +# API state at the start of the capture +BeginGroup(msg: GlobalStateᵈ{...}, id: 24) +Object(msg: Resourceᵈ{index: 1}) + ChildObject(msg: Observationᵈ{pool: 3, res_index: 1}, parentID: 24) +[...] +EndGroup(id: 24) +# List of commands +[...] +# Example of a command: vkQueueSubmit +## The command and its arguments +BeginGroup(msg: vkQueueSubmitᵈ{fence: 3916257488, pSubmits: 3141568848, ...}, id: 599) +## A few memory observations (resource + observation) +Object(msg: Resourceᵈ{data: [4 0 0 0 0 0 0 0 1 0 0 0 208 ...] (truncated 40 bytes), index: 12}) + ChildObject(msg: Observationᵈ{base: 3141568848, res_index: 12, size: 40}, parentID: 599) +Object(msg: Resourceᵈ{data: [240 87 109 233 0 0 0 0 176 110 109 233 0 0 0 0], index: 13}) + ChildObject(msg: Observationᵈ{base: 3838428368, res_index: 13, size: 16}, parentID: 599) +Object(msg: Resourceᵈ{data: [112 38 139 233], index: 14}) + ChildObject(msg: Observationᵈ{base: 3918206552, res_index: 14, size: 4}, parentID: 599) +## The actual call to the driver, and the return value (not being printed here as it is 0 == VK_SUCCESS) + ChildObject(msg: vkQueueSubmitCallᵈ{}, parentID: 599) +EndGroup(id: 599) +[...] +``` + +At the proto-pack level, we have `Object`, `ChildObject`, `BeginGroup` and +`EndGroup`. The `msg` fields are the protobuf messages. For instance, the +`Header` protobuf message is defined in `gapis/capture/capture.proto`. The +Vulkan-specific protobuf messages are defined in the `api.proto` file which is +generated from the `.api` files. + +We can see the capture header followed by `GlobalState`, the dump of Vulkan +state. Then, the rest of the capture is made of a series of Vulkan calls. Each +call is represented as a protopack group. This example illustrates how a +`vkQueueSubmit` call is encoded. + +In general, the call to a graphics API command leads to objects and messages +that represent: + +1. The API command and its arguments. + +2. Zero or more memory observations that represent memory that the driver may + read during this command. For instance, many Vulkan commands have pointers to + struct as arguments: the content of these structures is read by the driver. + +3. The actual call to the driver, and its return value (if any). + +4. Zero or more memory observations that represent memory that the driver may + have written to during its processing of the command. For instance, a + `vkCreateBuffer` call writes the handle of the newly create buffer to the + memory pointed at by its `VkBuffer* pBuffer` argument. + +Note that on multithreaded apps, the object groups may be interleaved. + +### GAPIS handling of a gfxtrace + +GAPIS parses gfxtrace files and represents them in a `GraphicsCapture` Go +object. This type gives access to the header, the initial state and the list of +commands, among other things. Once a gfxtrace file is loaded inside GAPIS, the +clients can use GAPIS protobuf interface to interact with the capture. + +For instance, a client may request the Vulkan state after a certain command. To +obtain this state, GAPIS will use the Go version of the `mutate` functions +(generated from the `.api` files) to mutate the initial state up to the required +command, and return the resulting state. Note that this does not require a +proper replay, as the state mutation happens entirely in GAPIS. + +The `.api` files can be seen here as an implementation of a driver for a given +graphics API, as they describe how each command affects the graphics API state. +Thus, it is possible to simulate the evolution of the graphics API state inside +GAPIS. This is what enables GAPIS to provide a snapshot of the state at some +point in the capture without having to do an actual replay. + +However, this driver simulation is not complete: the actual effects of draw +calls to their render targets are not implemented. This means that while GAPIS +does not need a replay to say e.g. how many images are in the state at a given +point, it does need a replay to show you the content of such images after some +draw calls. This replay is necessary as the result depends on the actual device +and driver used for replay. + +### Replay of a gfxtrace + +GAPIR is effectively a stack-based virtual machine specialized for the replay of +graphics API commands, see details in its own [README](gapir/README.md). To +replay a gfxtrace, GAPIS takes a `GraphicsCapture` object and generates a +payload made of GAPIR VM opcodes. + +The actual transformation of API commands into replay opcodes is made by the +`mutate` function of each command. These functions take a "replay builder" (see +`gapis/replay/builder` module) as an optional argument. When this builder is not +nil, the `mutate` function uses it to generate the replay opcodes corresponding +to the API command. + +When the initial state of a capture is not empty, GAPIS also generates "initial" +commands that are required to reconstruct the initial state from a fresh empty +state. For instance, if the initial state of a Vulkan capture has a Vulkan +instance, GAPIS generates a `vkCreateInstance` command to recreate a similar +instance. These initial commands must be replayed first in order to obtain a +state from wich the capture commands can be called. + +#### The command transformation framework + +One of the key feature of AGI is being able to replay variations of the original +capture. For instance, in order to see what the framebuffer looks like after a +certain draw call, AGI must make a replay up to the desired draw call, but no +further. In Vulkan, this typically requires to re-write the content of a command +buffer to only include the relevant draw calls. + +In order to implement such modifications on the capture, GAPIS has a command +transformation framework. Conceptually, the commands of a capture are streamed +into a chain of "transforms". A transform receives commands, may modify them, +and then pass the resulting commands to the next transform. At the end of the +chain, the obtained commands are mutated with a replay builder to obtain replay +opcodes. + +At a high-level, this can be compared to a series of unix pipes: + +``` +cat commands | transform1 | transform2 | ... | replay_builder > replay_payload +``` + +To see examples of Vulkan transforms, look at `gapis/api/vulkan/transform_*.go` +files. + +### GAPIR executes the replay instructions + +GAPIS uses the GAPIR protobuf service (`gapir/replay_service/service.proto`) to +request the replay of a payload. This payload contains replay opcodes, and +references to the required replay resources (i.e. the raw bytes of e.g. buffer +contents). + +The resources are not directly embedded in the replay payload: GAPIR lazily +requests them to GAPIS during the replay. On Android, GAPIR runs on the device +and communicates with GAPIS over ADB. Pushing the resources from GAPIS to GAPIR +is very slow, as they must be transferred over ADB. Because a replay may not +require all resources, and also because all resources may not fit in the replay +device memory, AGI refrains from uploading all resources upfront. Instead, GAPIR +maintains a resource cache and has some logic to request batches of resources to +lower the number of requests while keeping the cache full. + +During the replay, GAPIR can send back various information to GAPIS. For +instance, it can send back the content of a render target at a given point. It +also regularly sends notifications of how many instructions have been processed +so far, this information is used to reflect the progress of a replay in GAPIC. + +#### Split-replay: pre-warm replay with initial commands + +In order to speed-up replays, GAPIS requests the initial commands of a capture +to be replayed even before the user asks for a replay. + +Depending on the replay request, the capture commands may be transformed to +produce a relevant replay payload. Most of the time, these transformations only +need to be applied on the capture commands, not on the initial commands. Hence, +when waiting for user input, it makes sense to replay the default initial +commands to rebuilt the initial state: once the user requests a specific replay, +only the (transformed) capture commands needs to be replayed. + +If the user-requested replay does require to transform the initial commands, +then the pre-warm replay is abandonned and a new replay of the transformed +initial and capture commands is executed. + +## Life of a perfetto trace (system profile trace) + +AGI heavily relies on the [perfetto](https://perfetto.dev/) system profiling +framework to obtain, store and process profiling data. Hence we often refer to +system profile traces as "perfetto traces". If you have to deal with perfetto +code, make sure to refer to the [perfetto +documentation](https://perfetto.dev/docs/). + +Perfetto is built into Android since Android 9 Pie. In general, to take a +perfetto trace, you can use the `perfetto` command-line tool on the device. See +perfetto's web interface at https://ui.perfetto.dev/ and click on "Recording +command" to see an example of how the `perfetto` command-line tool can be used +to obtain a profiling trace. + +To take a trace, AGI may use either the `perfetto` command-line tool, or +perfetto's client interface. See e.g. +`gapis/perfetto/android/trace.go:Capture()` for how a capture is started on +Android. To see an example of AGI using perfetto's command line interface, see +`core/os/android/adb/perfetto.go:StartPerfettoTrace()`. Alternatively, AGI may +interact via perfetto's client interface by talking to the `traced` deamon +running on the device. This deamon listens to the `/dev/socket/traced_consumer`, +and AGI connects directly to this socket. The related AGI code is under +`gapis/perfetto/client/`. + +One specificity of GPU profiling is that some of the perfetto data producers are +inside GPU drivers, and they need to be started before a trace with these GPU +data sources can be taken. To start these GPU-specific perfetto data producers, +AGI has a small `agi_launch_producer` utility (see source in +`cmd/launch_producer/`). On Android, AGI extracts this utiliy from its own APK +and invokes it, such that the relevant GPU data producers are started (see +`gapidapk/gapidapk.go:EnsurePerfettoProducerLaunched()`). + +Once a perfetto trace has been collected, AGI uses perfetto's [trace +processor](https://perfetto.dev/docs/analysis/trace-processor) to retrieve +profiling data using SQL queries. diff --git a/README.md b/README.md index 944426312a..75661fdf8e 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,41 @@ -# GAPID: Graphics API Debugger +# Android GPU Inspector -[![GoDoc](https://godoc.org/github.com/google/gapid?status.svg)](https://godoc.org/github.com/google/gapid) -[![Gitter](https://badges.gitter.im/google/gapid.svg)](https://gitter.im/google/gapid?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + + +![]() Linux -[![Linux Build Status](https://gapid-build.storage.googleapis.com/badges/build_status_linux.svg)](https://gapid-build.storage.googleapis.com/badges/build_result_linux.html) +[![Linux Build Status](https://agi-build.storage.googleapis.com/badges/build_status_linux.svg)](https://agi-build.storage.googleapis.com/badges/build_result_linux.html) MacOS -[![MacOS Build Status](https://gapid-build.storage.googleapis.com/badges/build_status_macos.svg)](https://gapid-build.storage.googleapis.com/badges/build_result_macos.html) +[![MacOS Build Status](https://agi-build.storage.googleapis.com/badges/build_status_macos.svg)](https://agi-build.storage.googleapis.com/badges/build_result_macos.html) Windows -[![Windows Build Status](https://gapid-build.storage.googleapis.com/badges/build_status_windows.svg)](https://gapid-build.storage.googleapis.com/badges/build_result_windows.html) +[![Windows Build Status](https://agi-build.storage.googleapis.com/badges/build_status_windows.svg)](https://agi-build.storage.googleapis.com/badges/build_result_windows.html) -## Downloads +## About -**[Download the latest version of GAPID here.](https://github.com/google/gapid/releases)** +Visit [gpuinspector.dev](https://gpuinspector.dev) for information about Android GPU Inspector. -*Unstable* developer releases are [here](https://github.com/google/gapid-dev-releases/releases). +The [developer documentation](DEVDOC.md) contains some hints for AGI +developers. See also the README files under some source directories. -## Documentation +## Downloads -**[User documentation can be found at gapid.dev](https://gapid.dev)** +**[Download the latest version of AGI here.](https://github.com/google/agi/releases)** -The [developer documentation](DEVDOC.md) contains some hints for GAPID -developers. See also the README files under some source directories. +*Unstable* developer releases are [here](https://github.com/google/agi-dev-releases/releases). -## About - -GAPID is a collection of tools that allows you to inspect, tweak and replay calls from an application to a graphics driver. +Dependencies for Linux builds in zip archives: +- openjdk-11-jre +- libgtk-3-0 +- libwebkit2gtk -GAPID can trace any Android [debuggable application](https://developer.android.com/guide/topics/manifest/application-element.html#debug), or if you have root access to the device any application can be traced. -GAPID can also trace any desktop Vulkan application. +These are marked as dependencies in the .deb package. - - - - - - - - - -
- - Screenshot 1 - - - - Screenshot 2 - -
- - Screenshot 3 - - - - Screenshot 4 - -
+If you install AGI via a zip archive, make sure to install these dependencies as well. ## Building -**See [Building GAPID](BUILDING.md).** +**See [Building Android GPU Inspector](BUILDING.md).** ## Running the client -After building GAPID, you can run the client from `/bazel-bin/pkg/gapid`. - -## Command-Line Interface - -GAPID exposes most of its functionality via a CLI *gapit*. You can find auto-generated documentation [here](https://gapid.dev/cli/). - -## Project Structure - -GAPID consists of the following sub-components: - -### [`gapii`](gapii): Graphics API Interceptor -A layer that sits between the application / game and the GPU driver, recording all the calls and memory accesses. - -### [`gapis`](gapis): Graphics API Server -A process that analyses capture streams reporting incorrect API usage, processes the data for replay on various target devices, and provides an RPC interface to the client. - -### [`gapir`](gapir): Graphics API Replay daemon -A stack-based VM used to playback capture files, imitating the original application’s / game's calls to the GPU driver. Supports read-back of any buffer / framebuffer, and provides profiling functionality. - -### [`gapic`](gapic): Graphics API Client -The frontend user interface application. Provides visual inspection of the capture data, memory, resources, and frame-buffer content. - -### [`gapil`](gapil): Graphics API Language -A new domain specific language to describe a graphics API in its entirety. Combined with our template system to generate huge parts of the interceptor, server and replay systems. - +After building AGI, you can run the client from `/bazel-bin/pkg/agi`. diff --git a/WORKSPACE b/WORKSPACE index 92c0f514b8..634160889c 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -28,10 +28,39 @@ load("@gapid//tools/build:workspace_gapic.bzl", "gapic_dependencies", "gapic_thi gapic_dependencies(locals = LOCALS) gapic_third_party() +# Fuchsia rules, disabled by default. See .bazelrc for config to enable. +load("@gapid//tools/build/fuchsia:fuchsia_config.bzl", "fuchsia_config") +fuchsia_config() +load("@local_config_fuchsia//:workspace.bzl", "fuchsia_base_dependencies") +fuchsia_base_dependencies(locals = LOCALS) +load("@local_config_fuchsia//:fuchsia_sdk.bzl", "fuchsia_sdk_dependencies") +fuchsia_sdk_dependencies(locals = LOCALS) + load("@io_bazel_rules_go//go:deps.bzl", "go_rules_dependencies", "go_register_toolchains") load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") go_rules_dependencies() -go_register_toolchains() +go_register_toolchains("1.19") # gazelle:repo bazel_gazelle gazelle_dependencies() + +load("@com_github_grpc_grpc//bazel:grpc_deps.bzl", "grpc_deps") +grpc_deps() +load("@com_github_grpc_grpc//bazel:grpc_extra_deps.bzl", "grpc_extra_deps") +grpc_extra_deps() + +# Python setup. +load("@rules_python//python:repositories.bzl", "python_register_toolchains") + +python_register_toolchains( + name = "python3_9", + python_version = "3.9", +) + +load("@python3_9//:defs.bzl", "interpreter") +load("@rules_python//python:pip.bzl", "pip_install") + +pip_install( + python_interpreter_target = interpreter, + requirements = "//tools/build/python:requirements.txt", +) diff --git a/cmd/agi/BUILD.bazel b/cmd/agi/BUILD.bazel new file mode 100644 index 0000000000..96c262e599 --- /dev/null +++ b/cmd/agi/BUILD.bazel @@ -0,0 +1,49 @@ +# Copyright (C) 2018 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "console_other.go", + "console_windows.go", + "main.go", + ], + importpath = "github.com/google/gapid/cmd/agi", + visibility = ["//visibility:private"], +) + +# BUG: This isn't go_stripped_binary due to issue #1753. +go_binary( + name = "agi", + args = [ + "--jar", + "$(location //gapic:gapic_deploy.jar)", + ], + data = [ + "//cmd/gapis", + "//gapic:gapic_deploy.jar", + "//gapis/messages:stb", + ], + embed = [":go_default_library"], + gc_linkopts = select({ + "//tools/build:windows": [ + "-H", + "windowsgui", + ], + "//conditions:default": [], + }), + visibility = ["//visibility:public"], +) diff --git a/cmd/gapid/console_other.go b/cmd/agi/console_other.go similarity index 97% rename from cmd/gapid/console_other.go rename to cmd/agi/console_other.go index 95e53e2f86..32c1242445 100644 --- a/cmd/gapid/console_other.go +++ b/cmd/agi/console_other.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build !windows +//go:build !windows package main diff --git a/cmd/gapid/console_windows.go b/cmd/agi/console_windows.go similarity index 98% rename from cmd/gapid/console_windows.go rename to cmd/agi/console_windows.go index d4d5e22bb0..b2d2258b12 100644 --- a/cmd/gapid/console_windows.go +++ b/cmd/agi/console_windows.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build windows +//go:build windows package main diff --git a/cmd/agi/main.go b/cmd/agi/main.go new file mode 100644 index 0000000000..c1d5cf14b1 --- /dev/null +++ b/cmd/agi/main.go @@ -0,0 +1,297 @@ +// Copyright (C) 2017 Google Inc. +// +// 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. + +// The gapid command launches the GAPID UI. It looks for the JVM (bundled or +// from the system), the GAPIC JAR (bundled or from the build output) and +// launches GAPIC with the correct JVM flags and environment variables. +package main + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" +) + +const ( + versionPrefix = `version "` + googleInfix = "-google-" + minJavaMajor = 11 + minJavaMinor = 0 +) + +type config struct { + cwd string + vm string + vmArgs []string + gapic string + args []string + help bool + console bool + verbose bool +} + +func main() { + os.Exit(run()) +} + +func run() int { + c := newConfig() + + if c.console { + createConsole() + if runtime.GOOS == "windows" { + defer func() { + fmt.Println() + fmt.Println("Press enter to continue") + os.Stdin.Read(make([]byte, 1)) + }() + } + } + + if c.help { + defer func() { + fmt.Println() + fmt.Println("Launcher Flags:") + fmt.Println(" --jar Path to the gapic JAR to use") + fmt.Println(" --vm Path to the JVM to use") + fmt.Println(" --vmarg Extra argument for the JVM (repeatable)") + fmt.Println(" --console Run AGI inside a terminal console") + fmt.Println(" --verbose-startup Log verbosely in the launcher") + }() + } + + if err := c.locateCWD(); err != nil { + fmt.Println(err) + return 1 + } + + if err := c.locateVM(); err != nil { + fmt.Println(err) + if !c.verbose { + fmt.Println("Use --verbose-startup for additional details") + } + return 1 + } + + if err := c.locateGAPIC(); err != nil { + fmt.Println(err) + return 1 + } + + fmt.Println("Starting", c.vm, c.gapic) + cmd := exec.Command(c.vm, append(append(c.vmArgs, "-jar", c.gapic), c.args...)...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Env = append(os.Environ(), "GAPID="+c.cwd) + + if runtime.GOOS == "linux" { + cmd.Env = append(cmd.Env, "LIBOVERLAY_SCROLLBAR=0") + cmd.Env = append(cmd.Env, "GTK_OVERLAY_SCROLLING=0") + } + + // If run via 'bazel run', use the shell's CWD, not bazel's. + if cwd := os.Getenv("BUILD_WORKING_DIRECTORY"); cwd != "" { + cmd.Dir = cwd + } + + if err := cmd.Run(); err != nil { + if _, ok := err.(*exec.ExitError); !ok { + fmt.Println("Failed to start GAPIC:", err) + } + return 1 + } + return 0 +} + +func newConfig() *config { + c := &config{} + + // Doing our own flag handling (rather than using go's flag package) to avoid + // it attempting to parse the GAPIC flags, which may be in a different format. + // This loop simply looks for the launcher flags, but hands everything else to + // GAPIC verbatim. + args := os.Args[1:] + for i := 0; i < len(args); i++ { + switch { + case args[i] == "--jar" && i < len(args)-1: + i++ + c.gapic = args[i] + case args[i] == "--vm" && i < len(args)-1: + i++ + c.vm = args[i] + case args[i] == "--vmarg" && i < len(args)-1: + i++ + c.vmArgs = append(c.vmArgs, args[i]) + case args[i] == "--console": + c.console = true + case args[i] == "--verbose-startup": + c.verbose = true + default: + c.help = c.help || args[i] == "--help" || args[i] == "--fullhelp" + c.args = append(c.args, args[i]) + } + } + + c.console = c.console || c.help + + if runtime.GOOS == "darwin" || runtime.GOOS == "darwin_arm64" { + c.vmArgs = append(c.vmArgs, "-XstartOnFirstThread") + } + + return c +} + +func (c *config) logIfVerbose(args ...interface{}) { + if c.verbose { + fmt.Println(args...) + } +} + +func (c *config) locateCWD() error { + cwd, err := os.Executable() + if err != nil { + return err + } + cwd, err = filepath.EvalSymlinks(cwd) + if err == nil { + c.cwd = filepath.Dir(cwd) + c.logIfVerbose("CWD:", c.cwd) + } + return err +} + +func (c *config) locateVM() error { + if c.vm != "" { + if c.checkVM(c.vm, false) { + return nil + } + + if runtime.GOOS == "windows" && c.checkVM(c.vm+".exe", false) { + c.vm += ".exe" + return nil + } + + if java := c.javaInHome(c.vm); c.checkVM(java, false) { + c.vm = java + return nil + } + return fmt.Errorf("JVM '%s' not found/usable", c.vm) + } + + if java := c.javaInHome(filepath.Join(c.cwd, "jre")); c.checkVM(java, true) { + c.vm = java + return nil + } + + if runtime.GOOS == "linux" { + if java := "/usr/lib/jvm/java-11-openjdk-amd64/bin/java"; c.checkVM(java, true) { + c.vm = java + return nil + } + } + + if home := os.Getenv("JAVA_HOME"); home != "" { + if java := c.javaInHome(home); c.checkVM(java, true) { + c.vm = java + return nil + } + } + + if java, err := exec.LookPath(c.javaExecutable()); err == nil && c.checkVM(java, true) { + c.vm = java + return nil + } + + return fmt.Errorf("No suitable JVM found. A JRE >= %d.%d is required.", minJavaMajor, minJavaMinor) +} + +func (c *config) javaExecutable() string { + if runtime.GOOS == "windows" { + if c.console { + return "java.exe" + } + return "javaw.exe" + } + return "java" +} + +func (c *config) javaInHome(home string) string { + return filepath.Join(home, "bin", c.javaExecutable()) +} + +func (c *config) checkVM(java string, checkVersion bool) bool { + if stat, err := os.Stat(java); err != nil || stat.IsDir() { + c.logIfVerbose("Not using " + java + ": not a file") + return false + } + + if !checkVersion { + return true + } + + version, err := exec.Command(java, "-version").CombinedOutput() + if err != nil { + c.logIfVerbose("Not using " + java + ": failed to get version info") + return false + } + + versionStr := string(version) + + // Don't use the Google custom JDKs as they don't work with our JNI libs. + if p := strings.Index(versionStr, googleInfix); p >= 0 { + c.logIfVerbose("Not using " + java + ": is a Google JDK (go/gapid-jdk)") + return false + } + + // Looks for the pattern: version ".." + // Not using regular expressions to avoid binary bloat. + if p := strings.Index(versionStr, versionPrefix); p >= 0 { + p += len(versionPrefix) + if q := strings.Index(versionStr[p:], "."); q > 0 { + if r := strings.Index(versionStr[p+q+1:], "."); r > 0 { + major, _ := strconv.Atoi(versionStr[p : p+q]) + minor, _ := strconv.Atoi(versionStr[p+q+1 : p+q+r+1]) + useIt := major > minJavaMajor || (major == minJavaMajor && minor >= minJavaMinor) + if !useIt { + c.logIfVerbose("Not using " + java + ": unsupported version") + } + return useIt + } + } + } + + c.logIfVerbose("Not using " + java + ": failed to parse version") + return false +} + +func (c *config) locateGAPIC() error { + gapic := c.gapic + if gapic == "" { + gapic = filepath.Join(c.cwd, "lib", "gapic.jar") + } + if abs, err := filepath.Abs(gapic); err == nil { + gapic = abs + } + if _, err := os.Stat(gapic); !os.IsNotExist(err) { + c.gapic = gapic + return nil + } + + return fmt.Errorf("GAPIC JAR '%s' not found", gapic) +} diff --git a/cmd/apic/BUILD.bazel b/cmd/apic/BUILD.bazel index 480550cdbd..aec82c994f 100644 --- a/cmd/apic/BUILD.bazel +++ b/cmd/apic/BUILD.bazel @@ -18,7 +18,7 @@ go_library( name = "go_default_library", srcs = [ "binary.go", - "compile.go", + "encoders.go", "format.go", "main.go", "resolve.go", @@ -30,15 +30,11 @@ go_library( deps = [ "//core/app:go_default_library", "//core/log:go_default_library", - "//core/os/device:go_default_library", "//core/os/file:go_default_library", "//gapil:go_default_library", "//gapil/ast:go_default_library", "//gapil/bapi:go_default_library", - "//gapil/compiler:go_default_library", - "//gapil/compiler/mangling/c:go_default_library", - "//gapil/compiler/mangling/ia64:go_default_library", - "//gapil/compiler/plugins/encoder:go", + "//gapil/encoder:go_default_library", "//gapil/format:go_default_library", "//gapil/parser:go_default_library", "//gapil/resolver:go_default_library", diff --git a/cmd/apic/compile.go b/cmd/apic/compile.go deleted file mode 100644 index 0070e61d73..0000000000 --- a/cmd/apic/compile.go +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "os" - "strings" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/os/device" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/gapil/compiler" - "github.com/google/gapid/gapil/compiler/mangling/c" - "github.com/google/gapid/gapil/compiler/mangling/ia64" - "github.com/google/gapid/gapil/compiler/plugins/encoder" - "github.com/google/gapid/gapil/resolver" -) - -func init() { - app.AddVerb(&app.Verb{ - Name: "compile", - ShortHelp: "Emits code generated from .api files", - Action: &compileVerb{}, - }) -} - -type symbols int - -const ( - cSym = symbols(iota) - cppSym -) - -func (s symbols) String() string { - switch s { - case cSym: - return "c" - case cppSym: - return "c++" - default: - return "" - } -} - -func (s *symbols) Choose(v interface{}) { - *s = v.(symbols) -} - -type compileVerb struct { - Target string `help:"The target device ABI"` - Capture string `help:"The capture device ABI. Defaults to target"` - Output string `help:"The output file path"` - Module string `help:"The name of the global module variable to emit"` - Emit struct { - Clone bool `help:"Emit clone methods"` - Encode bool `help:"Emit encoder logic"` - Exec bool `help:"Emit executor logic. Implies --emit-context"` - Context bool `help:"Emit context constructor / destructor"` - Replay bool `help:"Emit replay generation methods"` - } - Namespace string `help:"Dot-delimited root namespace(s)"` - Symbols symbols `help:"Symbol generation method"` - Optimize bool `help:"Optimize generated code"` - Dump bool `help:"Dump LLVM IR to stderr"` - Search file.PathList `help:"The set of paths to search for includes"` -} - -func parseABI(s string) (*device.ABI, error) { - switch s { // Must match values in: tools/build/BUILD.bazel - case "": - return nil, nil // host - case "k8": - return device.LinuxX86_64, nil - case "darwin_x86_64": - return device.OSXX86_64, nil - case "x64_windows": - return device.WindowsX86_64, nil - case "armeabi-v7a": - return device.AndroidARMv7a, nil - case "arm64-v8a": - return device.AndroidARM64v8a, nil - case "x86": - return device.AndroidX86, nil - default: - return nil, fmt.Errorf("Unrecognised target: '%v'", s) - } -} - -func (v *compileVerb) Run(ctx context.Context, flags flag.FlagSet) error { - apis, mappings, err := resolve(ctx, flags.Args(), v.Search, resolver.Options{}) - if err != nil { - return err - } - - targetABI, err := parseABI(v.Target) - if err != nil { - return err - } - - captureABI := targetABI - if v.Capture != "" { - captureABI, err = parseABI(v.Capture) - if err != nil { - return err - } - } - - var namespaces []string - if v.Namespace != "" { - namespaces = strings.Split(v.Namespace, ".") - } - - settings := compiler.Settings{ - Module: v.Module, - TargetABI: targetABI, - CaptureABI: captureABI, - Namespaces: namespaces, - EmitExec: v.Emit.Exec, - EmitContext: v.Emit.Context, - } - - if v.Emit.Encode { - settings.Plugins = append(settings.Plugins, encoder.Plugin()) - } - if v.Emit.Clone { - //settings.Plugins = append(settings.Plugins, cloner.Plugin()) - return fmt.Errorf("The clone plugin is disabled in the apic build") - } - if v.Emit.Replay { - //settings.Plugins = append(settings.Plugins, replay.Plugin(nil)) - return fmt.Errorf("The replay plugin is disabled in the apic build") - } - - switch v.Symbols { - case cSym: - settings.Mangler = c.Mangle - default: - settings.Mangler = ia64.Mangle - } - - prog, err := compiler.Compile(apis, mappings, settings) - if err != nil { - return err - } - - if v.Optimize { - prog.Codegen.Optimize() - } - - if v.Dump { - fmt.Fprintln(os.Stderr, prog.Codegen.String()) - return fmt.Errorf("IR dump") - } - - obj, err := prog.Codegen.Object(v.Optimize) - if err != nil { - return err - } - - if err := ioutil.WriteFile(v.Output, obj, 0666); err != nil { - return err - } - - return nil -} diff --git a/cmd/apic/encoders.go b/cmd/apic/encoders.go new file mode 100644 index 0000000000..3455f927d7 --- /dev/null +++ b/cmd/apic/encoders.go @@ -0,0 +1,60 @@ +// Copyright (C) 2018 Google Inc. +// +// 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. + +package main + +import ( + "context" + "flag" + "os" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/os/file" + "github.com/google/gapid/gapil/encoder" + "github.com/google/gapid/gapil/resolver" +) + +func init() { + app.AddVerb(&app.Verb{ + Name: "encoders", + ShortHelp: "Emits generated code to encode types from .api files", + Action: &encodersVerb{}, + }) +} + +type encodersVerb struct { + Output string `help:"The output file path"` + Namespace string `help:"C++ namespace for the generated code"` + Search file.PathList `help:"The set of paths to search for includes"` +} + +func (v *encodersVerb) Run(ctx context.Context, flags flag.FlagSet) error { + apis, mappings, err := resolve(ctx, flags.Args(), v.Search, resolver.Options{}) + if err != nil { + return err + } + + out, err := os.Create(v.Output) + if err != nil { + return err + } + defer out.Close() + + settings := encoder.Settings{ + Namespace: v.Namespace, + Out: out, + } + + return encoder.GenerateEncoders(apis, mappings, settings) +} diff --git a/cmd/benchmark/BUILD.bazel b/cmd/benchmark/BUILD.bazel new file mode 100644 index 0000000000..64f082ad1e --- /dev/null +++ b/cmd/benchmark/BUILD.bazel @@ -0,0 +1,34 @@ +# Copyright (C) 2020 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/google/gapid/cmd/benchmark", + visibility = ["//visibility:private"], + deps = [ + "//core/app:go_default_library", + "//core/log:go_default_library", + "//core/os/file:go_default_library", + "//core/os/shell:go_default_library", + ], +) + +go_binary( + name = "benchmark", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/cmd/benchmark/main.go b/cmd/benchmark/main.go new file mode 100644 index 0000000000..6c1c7c192b --- /dev/null +++ b/cmd/benchmark/main.go @@ -0,0 +1,236 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +// The embed command is used to embed text files into Go executables as strings. +package main + +import ( + "context" + "encoding/csv" + "encoding/json" + "errors" + "flag" + "fmt" + "io/ioutil" + "os" + "strconv" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/file" + "github.com/google/gapid/core/os/shell" +) + +var ( + gapit string + root string +) + +func main() { + flag.StringVar(&gapit, "gapit", "gapit", "the path to the gapit command") + flag.StringVar(&root, "root", "", "the root directory to resolves paths against") + app.ShortHelp = "benchmark: A tool to run and summarize gapit benchmarks." + app.Run(run) +} + +func run(ctx context.Context) error { + cfg, err := readConfig() + if err != nil { + return err + } + + tmpOut, err := ioutil.TempDir("", "benchmark") + if err != nil { + return err + } + defer os.RemoveAll(tmpOut) + + res := []*results{} + for i := range cfg.Traces { + r, err := runTrace(ctx, cfg, i, file.Abs(tmpOut)) + if err != nil { + return err + } + res = append(res, r) + } + + fmt.Println("------------------------") + printResults(res) + fmt.Println("------------------------") + printSummary(res) + + return nil +} + +type results struct { + name string + titles []string + values [][]float64 +} + +func (r *results) append(path file.Path) error { + in, err := os.Open(path.System()) + if err != nil { + return err + } + defer in.Close() + + records, err := csv.NewReader(in).ReadAll() + if err != nil { + return err + } + + if len(records) != 2 { + return fmt.Errorf("Expected two summary rows, got %d", len(records)) + } + + if r.titles != nil && len(r.titles) != len(records[0]) { + return fmt.Errorf("Unmatched number of titles: got %d, expected %d", len(records[0]), len(r.titles)) + } + r.titles = records[0] + + if len(records[0]) != len(records[1]) { + return fmt.Errorf("Unmatched number of values: got %d, expected %d", len(records[1]), len(records[0])) + } + + values := make([]float64, len(records[1])) + for i := range values { + v, err := strconv.ParseFloat(records[1][i], 64) + if err != nil { + return fmt.Errorf("Failed to parse summary value \"%s\": %v", records[1][i], err) + } + values[i] = v + } + r.values = append(r.values, values) + + return nil +} + +func runTrace(ctx context.Context, cfg *config, idx int, tmpOut file.Path) (*results, error) { + trace := cfg.Traces[idx] + log.I(ctx, "Measuring %v (%v)...", trace.Name, trace.Input) + + res := &results{ + name: trace.Name, + } + for i := 0; i < cfg.Iterations; i++ { + summaryOut := tmpOut.Join(fmt.Sprintf("summry_%d_%d.csv", idx, i)) + args := []string{ + "benchmark", + "--numdraws", strconv.Itoa(cfg.Draws), + "--summaryout", summaryOut.System(), + } + if trace.Secondary { + args = append(args, "--secondary") + } + for _, path := range trace.Paths { + args = append(args, "--paths", path) + } + args = append(args, file.Abs(root).Join(trace.Input).System()) + + err := shell.Command(gapit, args...). + Verbose(). + In(root). + Run(ctx) + if err != nil { + return nil, err + } + + if err := res.append(summaryOut); err != nil { + return nil, err + } + } + + return res, nil +} + +type config struct { + Iterations int + Draws int + Traces []struct { + Name string + Input string + Paths []string + Secondary bool + } +} + +func readConfig() (*config, error) { + args := flag.Args() + if len(args) != 1 { + return nil, errors.New("Expected a config file as a paramter") + } + + in, err := os.Open(args[0]) + if err != nil { + return nil, err + } + defer in.Close() + + dec := json.NewDecoder(in) + c := config{ + Iterations: 1, + } + if err := dec.Decode(&c); err != nil { + return nil, err + } + + if len(c.Traces) == 0 { + return nil, errors.New("No traces in config file, expected at least one") + } + + return &c, nil +} + +func printResults(rs []*results) { + out := csv.NewWriter(os.Stdout) + out.Write(append([]string{"Application"}, rs[0].titles...)) + + for _, r := range rs { + for _, vs := range r.values { + printResultRow(out, r.name, vs) + } + } + + out.Flush() +} + +func printSummary(rs []*results) { + out := csv.NewWriter(os.Stdout) + out.Write(append([]string{"Application"}, rs[0].titles...)) + + for _, r := range rs { + avgs := make([]float64, len(r.values[0])) + for _, vs := range r.values { + for i := range avgs { + avgs[i] += vs[i] + } + } + for i := range avgs { + avgs[i] /= float64(len(r.values)) + } + printResultRow(out, r.name, avgs) + } + + out.Flush() +} + +func printResultRow(out *csv.Writer, name string, row []float64) { + r := make([]string, 1+len(row)) + r[0] = name + for i, v := range row { + r[i+1] = fmt.Sprintf("%0.3f", v) + } + out.Write(r) +} diff --git a/cmd/device-info/main.cpp b/cmd/device-info/main.cpp index fa3d013562..196d05f61a 100644 --- a/cmd/device-info/main.cpp +++ b/cmd/device-info/main.cpp @@ -68,9 +68,9 @@ int main(int argc, char const* argv[]) { std::string output; google::protobuf::util::JsonPrintOptions options; options.add_whitespace = true; - if (google::protobuf::util::Status::OK != - google::protobuf::util::MessageToJsonString(device_inst, &output, - options)) { + if (!google::protobuf::util::MessageToJsonString(device_inst, &output, + options) + .ok()) { GAPID_ERROR("Internal error: Could not convert to json"); free_device_instance(instance); return -1; diff --git a/cmd/enum_lookup/BUILD.bazel b/cmd/enum_lookup/BUILD.bazel index 7401d0d48b..a925e46811 100644 --- a/cmd/enum_lookup/BUILD.bazel +++ b/cmd/enum_lookup/BUILD.bazel @@ -15,22 +15,6 @@ load("//tools/build:rules.bzl", "apic_template", "go_stripped_binary") load("@io_bazel_rules_go//go:def.bzl", "go_library") -apic_template( - name = "gles_lookup", - api = "//gapis/api/gles:api", - templates = [ - "//gapis/api/templates:enum_lookup.go", - ], -) - -apic_template( - name = "gvr_lookup", - api = "//gapis/api/gvr:api", - templates = [ - "//gapis/api/templates:enum_lookup.go", - ], -) - apic_template( name = "vulkan_lookup", api = "//gapis/api/vulkan:api", @@ -46,8 +30,6 @@ go_library( "main.go", ], embed = [ - ":gles_lookup", # keep - ":gvr_lookup", # keep ":vulkan_lookup", # keep ], importpath = "github.com/google/gapid/cmd/enum_lookup", diff --git a/cmd/gapid/BUILD.bazel b/cmd/gapid/BUILD.bazel deleted file mode 100644 index 4c483927bc..0000000000 --- a/cmd/gapid/BUILD.bazel +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "console_other.go", - "console_windows.go", - "main.go", - ], - importpath = "github.com/google/gapid/cmd/gapid", - visibility = ["//visibility:private"], -) - -# BUG: This isn't go_stripped_binary due to issue #1753. -go_binary( - name = "gapid", - args = [ - "--jar", - "$(location //gapic:gapic_deploy.jar)", - ], - data = [ - "//cmd/gapis", - "//cmd/gapit", - "//gapic:gapic_deploy.jar", - "//gapis/messages:stb", - ], - embed = [":go_default_library"], - gc_linkopts = select({ - "//tools/build:windows": [ - "-H", - "windowsgui", - ], - "//conditions:default": [], - }), - visibility = ["//visibility:public"], -) diff --git a/cmd/gapid/main.go b/cmd/gapid/main.go deleted file mode 100644 index cd57a6f5a5..0000000000 --- a/cmd/gapid/main.go +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// The gapid command launches the GAPID UI. It looks for the JVM (bundled or -// from the system), the GAPIC JAR (bundled or from the build output) and -// launches GAPIC with the correct JVM flags and environment variables. -package main - -import ( - "errors" - "fmt" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" -) - -const ( - versionPrefix = `version "` - googleInfix = "-google-" - minJavaMajor = 1 - minJavaMinor = 8 -) - -type config struct { - cwd string - vm string - vmArgs []string - gapic string - args []string - help bool - console bool - verbose bool -} - -func main() { - os.Exit(run()) -} - -func run() int { - c := newConfig() - - if c.console { - createConsole() - if runtime.GOOS == "windows" { - defer func() { - fmt.Println() - fmt.Println("Press enter to continue") - os.Stdin.Read(make([]byte, 1)) - }() - } - } - - if c.help { - defer func() { - fmt.Println() - fmt.Println("Launcher Flags:") - fmt.Println(" --jar Path to the gapic JAR to use") - fmt.Println(" --vm Path to the JVM to use") - fmt.Println(" --vmarg Extra argument for the JVM (repeatable)") - fmt.Println(" --console Run GAPID inside a terminal console") - fmt.Println(" --verbose-startup Log verbosely in the launcher") - }() - } - - if err := c.locateCWD(); err != nil { - fmt.Println(err) - return 1 - } - - if err := c.locateVM(); err != nil { - fmt.Println(err) - if !c.verbose { - fmt.Println("Use --verbose-startup for additional details") - } - return 1 - } - - if err := c.locateGAPIC(); err != nil { - fmt.Println(err) - return 1 - } - - fmt.Println("Starting", c.vm, c.gapic) - cmd := exec.Command(c.vm, append(append(c.vmArgs, "-jar", c.gapic), c.args...)...) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - cmd.Env = append(os.Environ(), "GAPID="+c.cwd) - - if runtime.GOOS == "linux" { - cmd.Env = append(cmd.Env, "SWT_GTK3=0", "LIBOVERLAY_SCROLLBAR=0") - } - - // If run via 'bazel run', use the shell's CWD, not bazel's. - if cwd := os.Getenv("BUILD_WORKING_DIRECTORY"); cwd != "" { - cmd.Dir = cwd - } - - if err := cmd.Run(); err != nil { - if _, ok := err.(*exec.ExitError); !ok { - fmt.Println("Failed to start GAPIC:", err) - } - return 1 - } - return 0 -} - -func newConfig() *config { - c := &config{} - - // Doing our own flag handling (rather than using go's flag package) to avoid - // it attempting to parse the GAPIC flags, which may be in a different format. - // This loop simply looks for the launcher flags, but hands everything else to - // GAPIC verbatim. - args := os.Args[1:] - for i := 0; i < len(args); i++ { - switch { - case args[i] == "--jar" && i < len(args)-1: - i++ - c.gapic = args[i] - case args[i] == "--vm" && i < len(args)-1: - i++ - c.vm = args[i] - case args[i] == "--vmarg" && i < len(args)-1: - i++ - c.vmArgs = append(c.vmArgs, args[i]) - case args[i] == "--console": - c.console = true - case args[i] == "--verbose-startup": - c.verbose = true - default: - c.help = c.help || args[i] == "--help" || args[i] == "--fullhelp" - c.args = append(c.args, args[i]) - } - } - - c.console = c.console || c.help - - if runtime.GOOS == "darwin" { - c.vmArgs = append(c.vmArgs, "-XstartOnFirstThread") - } - - return c -} - -func (c *config) logIfVerbose(args ...interface{}) { - if c.verbose { - fmt.Println(args...) - } -} - -func (c *config) locateCWD() error { - cwd, err := os.Executable() - if err != nil { - return err - } - cwd, err = filepath.EvalSymlinks(cwd) - if err == nil { - c.cwd = filepath.Dir(cwd) - c.logIfVerbose("CWD:", c.cwd) - } - return err -} - -func (c *config) locateVM() error { - if c.vm != "" { - if c.checkVM(c.vm, false) { - return nil - } - - if runtime.GOOS == "windows" && c.checkVM(c.vm+".exe", false) { - c.vm += ".exe" - return nil - } - - if java := c.javaInHome(c.vm); c.checkVM(java, false) { - c.vm = java - return nil - } - return fmt.Errorf("JVM '%s' not found/usable", c.vm) - } - - if java := c.javaInHome(filepath.Join(c.cwd, "jre")); c.checkVM(java, true) { - c.vm = java - return nil - } - - if home := os.Getenv("JAVA_HOME"); home != "" { - if java := c.javaInHome(home); c.checkVM(java, true) { - c.vm = java - return nil - } - } - - if java, err := exec.LookPath(c.javaExecutable()); err == nil && c.checkVM(java, true) { - c.vm = java - return nil - } - - if runtime.GOOS == "linux" { - if java := "/usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java"; c.checkVM(java, true) { - c.vm = java - return nil - } else { - fmt.Println("To get a suitable JVM on Linux, install a regular (non-Google) JVM version >= 8 (e.g. openjdk-jre-8)") - } - } - - return errors.New("No suitable JVM found") -} - -func (c *config) javaExecutable() string { - if runtime.GOOS == "windows" { - if c.console { - return "java.exe" - } - return "javaw.exe" - } - return "java" -} - -func (c *config) javaInHome(home string) string { - return filepath.Join(home, "bin", c.javaExecutable()) -} - -func (c *config) checkVM(java string, checkVersion bool) bool { - if stat, err := os.Stat(java); err != nil || stat.IsDir() { - c.logIfVerbose("Not using " + java + ": not a file") - return false - } - - if !checkVersion { - return true - } - - version, err := exec.Command(java, "-version").CombinedOutput() - if err != nil { - c.logIfVerbose("Not using " + java + ": failed to get version info") - return false - } - - versionStr := string(version) - - // Blacklist the Google custom JDKs. - if p := strings.Index(versionStr, googleInfix); p >= 0 { - c.logIfVerbose("Not using " + java + ": is a Google JDK (go/gapid-jdk)") - return false - } - - // Looks for the pattern: version ".." - // Not using regular expressions to avoid binary bloat. - if p := strings.Index(versionStr, versionPrefix); p >= 0 { - p += len(versionPrefix) - if q := strings.Index(versionStr[p:], "."); q > 0 { - if r := strings.Index(versionStr[p+q+1:], "."); r > 0 { - major, _ := strconv.Atoi(versionStr[p : p+q]) - minor, _ := strconv.Atoi(versionStr[p+q+1 : p+q+r+1]) - useIt := major > minJavaMajor || (major == minJavaMajor && minor >= minJavaMinor) - if !useIt { - c.logIfVerbose("Not using " + java + ": unsupported version") - } - return useIt - } - } - } - - c.logIfVerbose("Not using " + java + ": failed to parse version") - return false -} - -func (c *config) locateGAPIC() error { - gapic := c.gapic - if gapic == "" { - gapic = filepath.Join(c.cwd, "lib", "gapic.jar") - } - if abs, err := filepath.Abs(gapic); err == nil { - gapic = abs - } - if _, err := os.Stat(gapic); !os.IsNotExist(err) { - c.gapic = gapic - return nil - } - - return fmt.Errorf("GAPIC JAR '%s' not found", gapic) -} diff --git a/cmd/gapir/cc/BUILD.bazel b/cmd/gapir/cc/BUILD.bazel index db2c7a7194..cd6f3f9b95 100644 --- a/cmd/gapir/cc/BUILD.bazel +++ b/cmd/gapir/cc/BUILD.bazel @@ -24,6 +24,7 @@ cc_library( "//tools/build:linux": [], "//tools/build:windows": [], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], # Android "//conditions:default": [ "@android_native_app_glue//:native_app_glue", diff --git a/cmd/gapir/cc/main.cpp b/cmd/gapir/cc/main.cpp index d21bf2dd7d..0c1ee59d25 100644 --- a/cmd/gapir/cc/main.cpp +++ b/cmd/gapir/cc/main.cpp @@ -44,7 +44,9 @@ #include #if TARGET_OS == GAPID_OS_ANDROID +#include #include +#include #include "android_native_app_glue.h" #include "gapir/cc/android/asset_replay_service.h" #include "gapir/cc/android/asset_resource_cache.h" @@ -59,6 +61,11 @@ using namespace gapir; namespace { +// kSocketName must match "socketName" in gapir/client/device_connection.go +const std::string kSocketName("gapir-socket"); + +std::unique_ptr server = nullptr; + std::shared_ptr createAllocator() { #if defined(__x86_64) || defined(__aarch64__) size_t size = 16ull * 1024ull * 1024ull * 1024ull; @@ -66,6 +73,15 @@ std::shared_ptr createAllocator() { size_t size = 2ull * 1024ull * 1024ull * 1024ull; #endif +#if TARGET_OS == GAPID_OS_ANDROID + // On Hardware ASAN builds, limit ourselves to 1G, so the sanitizer is happy. + char abis[PROP_VALUE_MAX] = {0}; + if ((__system_property_get("ro.product.cpu.abilist", abis) != 0) && + (strstr(abis, "-hwasan") != nullptr)) { + size = 1ull * 1024ull * 1024ull * 1024ull; + } +#endif + return std::shared_ptr(new MemoryAllocator(size)); } @@ -319,12 +335,27 @@ std::unique_ptr Setup(const char* uri, const char* authToken, core::CrashHandler* crashHandler, MemoryManager* memMgr, PrewarmData* prewarm, std::mutex* lock) { - // Return a replay server with the following replay ID handler. The first - // package for a replay must be the ID of the replay. + // Return a replay server with the following ReplayHandler. return Server::createAndStart( uri, authToken, idleTimeoutSec, [cache, memMgr, crashHandler, lock, prewarm](GrpcReplayService* replayConn) { + // This lambda implements the ReplayHandler. Any error should be + // reported to GAPIS. Benign errors (e.g. Vulkan errors collected + // during a "report" replay) are sent back through replay + // notifications. All other errors (e.g. failure during priming) should + // be handled with GAPID_FATAL(): the crash handler will notify GAPIS, + // which will be aware of the replay failure and will restart the + // replayer. In any case, do NOT fail silently via an early return, + // otherwise GAPIS may hang on waiting for a replay response. The only + // clean termination for this ReplayHandler is to return when there is + // no more replay requests to process, which reflects the fact that the + // GAPIS-GAPIR connection has been terminated. + + std::unique_ptr crash_uploader = + std::unique_ptr( + new CrashUploader(*crashHandler, replayConn)); + std::unique_ptr resLoader; if (cache == nullptr) { resLoader = PassThroughResourceLoader::create(replayConn); @@ -333,16 +364,10 @@ std::unique_ptr Setup(const char* uri, const char* authToken, cache, PassThroughResourceLoader::create(replayConn)); } - std::unique_ptr crash_uploader = - std::unique_ptr( - new CrashUploader(*crashHandler, replayConn)); - std::unique_ptr context = Context::create(replayConn, *crashHandler, resLoader.get(), memMgr); - if (context == nullptr) { - GAPID_ERROR("Loading Context failed!"); - return; + GAPID_FATAL("Loading Context failed!"); } auto cleanup_state = [&](bool isPrewarm) { @@ -396,6 +421,7 @@ std::unique_ptr Setup(const char* uri, const char* authToken, return true; }; + // Loop on getting and processing replay requests do { auto req = replayConn->getReplayRequest(); if (!req) { @@ -420,8 +446,7 @@ std::unique_ptr Setup(const char* uri, const char* authToken, if (context->initialize(req->replay().replay_id())) { GAPID_INFO("Replay context initialized successfully"); } else { - GAPID_ERROR("Replay context initialization failed"); - continue; + GAPID_FATAL("Replay context initialization failed"); } if (cache != nullptr) { context->prefetch(cache); @@ -432,7 +457,7 @@ std::unique_ptr Setup(const char* uri, const char* authToken, GAPID_INFO("Replay %s", ok ? "finished successfully" : "failed"); replayConn->sendReplayFinished(); if (!context->cleanup()) { - return; + GAPID_FATAL("Context cleanup failed"); } prewarm->current_state = ""; if (prewarm->prewarm_service && !prewarm->prewarm_id.empty() && @@ -454,20 +479,18 @@ std::unique_ptr Setup(const char* uri, const char* authToken, } if (prewarm->current_state != "") { if (!cleanup_state(true)) { - GAPID_ERROR( + GAPID_FATAL( "Could not clean up after previous replay, in a bad " "state now"); - return; } } if (!prime_state(std::move(req->prewarm().prerun_id()), std::move(req->prewarm().cleanup_id()), true)) { - GAPID_ERROR("Could not prime state: in a bad state now"); - return; + GAPID_FATAL("Could not prime state: in a bad state now"); } break; } - default: { break; } + default: { GAPID_FATAL("Unknown replay request type"); } } } while (true); }); @@ -532,20 +555,6 @@ int jni_call_i(JNIEnv* env, jobject obj, const char* name, const char* sig, return env->CallIntMethod(obj, mid, std::forward(args)...); } -const char* pipeName() { -#ifdef __x86_64 - return "gapir-x86-64"; -#elif defined __i386 - return "gapir-x86"; -#elif defined __ARM_ARCH_7A__ - return "gapir-arm"; -#elif defined __aarch64__ - return "gapir-arm64"; -#else -#error "Unrecognised target architecture" -#endif -} - void android_process(struct android_app* app, int32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: { @@ -553,6 +562,16 @@ void android_process(struct android_app* app, int32_t cmd) { GAPID_DEBUG("Received window: %p\n", gapir::android_window); break; } + case APP_CMD_PAUSE: { + GAPID_WARNING( + "Exiting because of APP_CMD_PAUSE. GAPIR does not work correctly " + "from pause."); + if (server != nullptr) { + server->shutdown(); + } + ANativeActivity_finish(app->activity); + break; + } } } @@ -681,7 +700,7 @@ void android_main(struct android_app* app) { Options::PrintHelp(); return; } else if (opts.version) { - GAPID_INFO("GAPIR version " GAPID_VERSION_AND_BUILD "\n"); + GAPID_INFO("GAPIR version " AGI_VERSION_AND_BUILD "\n"); return; } else if (opts.mode == kConflict) { GAPID_ERROR("Argument conflicts."); @@ -693,15 +712,16 @@ void android_main(struct android_app* app) { CrashHandler crashHandler(getCacheDir(app)); + ANativeActivity_setWindowFlags( + app->activity, AWINDOW_FLAG_KEEP_SCREEN_ON | AWINDOW_FLAG_FULLSCREEN, 0); + std::thread waiting_thread; std::atomic thread_is_done(false); // Get the path of the file system socket. - const char* pipe = pipeName(); std::string internal_data_path = std::string(app->activity->internalDataPath); - std::string socket_file_path = internal_data_path + "/" + std::string(pipe); + std::string socket_file_path = internal_data_path + "/" + kSocketName; std::string uri = std::string("unix://") + socket_file_path; - std::unique_ptr server = nullptr; std::shared_ptr allocator = createAllocator(); MemoryManager memoryManager(allocator); auto cache = @@ -974,7 +994,7 @@ int main(int argc, const char* argv[]) { Options::PrintHelp(); return EXIT_SUCCESS; } else if (opts.version) { - printf("GAPIR version " GAPID_VERSION_AND_BUILD "\n"); + printf("GAPIR version " AGI_VERSION_AND_BUILD "\n"); return EXIT_SUCCESS; } else if (opts.mode == kConflict) { GAPID_ERROR("Argument conflicts."); diff --git a/cmd/gapis/BUILD.bazel b/cmd/gapis/BUILD.bazel index 84f0195f9e..c621c175f1 100644 --- a/cmd/gapis/BUILD.bazel +++ b/cmd/gapis/BUILD.bazel @@ -28,14 +28,12 @@ go_library( "//core/log:go_default_library", "//core/os/android/adb:go_default_library", "//core/os/device/bind:go_default_library", - "//core/os/device/ggp:go_default_library", - "//core/os/device/host:go_default_library", "//core/os/device/remotessh:go_default_library", "//core/os/file:go_default_library", + "//core/os/fuchsia/ffx:go_default_library", "//core/text:go_default_library", "//gapir/client:go_default_library", "//gapis/database:go_default_library", - "//gapis/extensions/unity:go_default_library", "//gapis/replay:go_default_library", "//gapis/server:go_default_library", "//gapis/service:go_default_library", @@ -50,7 +48,19 @@ go_stripped_binary( name = "gapis", data = [ "//cmd/gapir/cc:gapir", + "//core/vulkan/vk_virtual_swapchain/cc:json", + "//core/vulkan/vk_virtual_swapchain/cc:libVkLayer_VirtualSwapchain", + "//gapii/cc:libgapii", + "//gapii/vulkan/vk_graphics_spy/cc:json", ] + select({ + "@gapid//tools/build:linux": [ + "//core/vulkan/vk_api_timing_layer/cc:json", + "//core/vulkan/vk_api_timing_layer/cc:libVkLayer_CPUTiming", + "//core/vulkan/vk_memory_tracker_layer/cc:json", + "//core/vulkan/vk_memory_tracker_layer/cc:libVkLayer_MemoryTracker", + ], + "//conditions:default": [], + }) + select({ "//tools/build:no-android": [], "//conditions:default": [ "//gapidapk/android/apk:arm64-v8a.apk", diff --git a/cmd/gapis/main.go b/cmd/gapis/main.go index 8d0b5a14b6..2c26ac5a83 100644 --- a/cmd/gapis/main.go +++ b/cmd/gapis/main.go @@ -32,10 +32,9 @@ import ( "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/android/adb" "github.com/google/gapid/core/os/device/bind" - "github.com/google/gapid/core/os/device/ggp" - "github.com/google/gapid/core/os/device/host" "github.com/google/gapid/core/os/device/remotessh" "github.com/google/gapid/core/os/file" + "github.com/google/gapid/core/os/fuchsia/ffx" "github.com/google/gapid/core/text" "github.com/google/gapid/gapir/client" "github.com/google/gapid/gapis/database" @@ -45,9 +44,6 @@ import ( "github.com/google/gapid/gapis/service/path" "github.com/google/gapid/gapis/stringtable" "github.com/google/gapid/gapis/trace" - - // Extensions - _ "github.com/google/gapid/gapis/extensions/unity" ) var ( @@ -58,11 +54,14 @@ var ( gapirAuthToken = flag.String("gapir-auth-token", "", "_The connection authorization token for gapir") gapirArgStr = flag.String("gapir-args", "", "_The arguments to be passed to the host-run gapir") scanAndroidDevs = flag.Bool("monitor-android-devices", true, "Server will scan for locally connected Android devices") + scanFuchsiaDevs = flag.Bool("monitor-fuchsia-devices", true, "Server will scan for locally connected Fuchsia devices") + androidSerial = flag.String("android-serial", "", "Server will only consider the Android device with this serial id") addLocalDevice = flag.Bool("add-local-device", true, "Server can trace and replay locally") - idleTimeout = flag.Duration("idle-timeout", 0, "_Closes GAPIS if the server is not repeatedly pinged within this duration") + idleTimeout = flag.Duration("idle-timeout", 0, "_Closes GAPIS if the server is not repeatedly pinged within this duration (e.g. '30s', '2m'). Default: 0 (no timeout).") adbPath = flag.String("adb", "", "Path to the adb executable; leave empty to search the environment") enableLocalFiles = flag.Bool("enable-local-files", false, "Allow clients to access local .gfxtrace files by path") remoteSSHConfig = flag.String("ssh-config", "", "_Path to an ssh config file for remote devices") + preloadDepGraph = flag.Bool("preload-dep-graph", true, "_Preload the dependency graph when loading captures") ) func main() { @@ -98,6 +97,16 @@ func run(ctx context.Context) error { adb.ADB = file.Abs(*adbPath) } + // Check if FFX SDK path is set, which is necessary to scan Fuchsia devices + if *scanFuchsiaDevs { + _, ffxFound := os.LookupEnv("FUCHSIA_FFX_PATH") + if !ffxFound { + // Disable Fuchsia device scanning when there is no SDK, to avoid runtime errors + log.W(ctx, "FUCHSIA_FFX_PATH is not set, disabling Fuchsia device monitoring.") + *scanFuchsiaDevs = false + } + } + r := bind.NewRegistry() ctx = bind.PutRegistry(ctx, r) m := replay.New(ctx) @@ -119,28 +128,39 @@ func run(ctx context.Context) error { wg := sync.WaitGroup{} + if *androidSerial != "" { + adb.LimitToSerial(*androidSerial) + } + if *scanAndroidDevs { wg.Add(1) crash.Go(func() { monitorAndroidDevices(ctx, r, wg.Done) }) } + if *scanFuchsiaDevs { + wg.Add(1) + crash.Go(func() { monitorFuchsiaDevices(ctx, r, wg.Done) }) + } + if *remoteSSHConfig != "" { wg.Add(1) crash.Go(func() { monitorRemoteSSHDevices(ctx, r, wg.Done) }) } - wg.Add(1) - crash.Go(func() { monitorGGPDevices(ctx, r, wg.Done) }) - deviceScanDone, onDeviceScanDone := task.NewSignal() crash.Go(func() { wg.Wait() onDeviceScanDone(ctx) }) + hostname, err := os.Hostname() + if err != nil { + return log.Err(ctx, err, "Failed to retrieve hostname") + } + return server.Listen(ctx, *rpc, server.Config{ Info: &service.ServerInfo{ - Name: host.Instance(ctx).Name, + Name: hostname, VersionMajor: uint32(app.Version.Major), VersionMinor: uint32(app.Version.Minor), VersionPoint: uint32(app.Version.Point), @@ -149,6 +169,7 @@ func run(ctx context.Context) error { }, StringTables: loadStrings(ctx), EnableLocalFiles: *enableLocalFiles, + PreloadDepGraph: *preloadDepGraph, AuthToken: auth.Token(*gapisAuthToken), DeviceScanDone: deviceScanDone, LogBroadcaster: logBroadcaster, @@ -174,6 +195,23 @@ func monitorAndroidDevices(ctx context.Context, r *bind.Registry, scanDone func( } } +func monitorFuchsiaDevices(ctx context.Context, r *bind.Registry, scanDone func()) { + // Populate the registry with all the existing devices. + func() { + defer scanDone() // Signal that we have a primed registry. + + if devs, err := ffx.Devices(ctx); err == nil { + for _, d := range devs { + r.AddDevice(ctx, d) + } + } + }() + + if err := ffx.Monitor(ctx, r, time.Second*3); err != nil { + log.W(ctx, "Could not scan for local Fuchsia devices. Error: %v", err) + } +} + func monitorRemoteSSHDevices(ctx context.Context, r *bind.Registry, scanDone func()) { getRemoteSSHConfig := func() ([]io.ReadCloser, error) { f, err := os.Open(*remoteSSHConfig) @@ -206,25 +244,6 @@ func monitorRemoteSSHDevices(ctx context.Context, r *bind.Registry, scanDone fun } } -func monitorGGPDevices(ctx context.Context, r *bind.Registry, scanDone func()) { - - func() { - // Populate the registry with all the existing devices. - defer scanDone() // Signal that we have a primed registry. - - if devs, err := ggp.Devices(ctx); err == nil { - for _, d := range devs { - r.AddDevice(ctx, d) - r.SetDeviceProperty(ctx, d, client.LaunchArgsKey, text.SplitArgs(*gapirArgStr)) - } - } - }() - - if err := ggp.Monitor(ctx, r, time.Second*15); err != nil { - log.W(ctx, "Could not scan for remote GGP devices. Error: %v", err) - } -} - func loadStrings(ctx context.Context) []*stringtable.StringTable { files, err := filepath.Glob(filepath.Join(*stringsPath, "*.stb")) if err != nil { diff --git a/cmd/gapit/BUILD.bazel b/cmd/gapit/BUILD.bazel index c924bf172f..4873487958 100644 --- a/cmd/gapit/BUILD.bazel +++ b/cmd/gapit/BUILD.bazel @@ -31,6 +31,7 @@ go_library( "dump_shaders.go", "export_replay.go", "flags.go", + "framegraph.go", "inputs.go", "main.go", "make_doc.go", @@ -41,12 +42,15 @@ go_library( "replace_resource.go", "report.go", "screenshot.go", + "server_performance.go", + "split.go", "state.go", "status.go", "stresstest.go", "sxs_video.go", "trace.go", "trim.go", + "trim_state.go", "unpack.go", "validate_gpu_profiling.go", "video.go", @@ -77,6 +81,7 @@ go_library( "//core/os/device/remotessh:go_default_library", "//core/os/file:go_default_library", "//core/os/shell:go_default_library", + "//core/stream/fmts:go_default_library", "//core/text/reflow:go_default_library", "//core/video:go_default_library", "//gapir/replay_service:go_default_library", @@ -89,7 +94,9 @@ go_library( "//gapis/service/path:go_default_library", "//gapis/service/types:go_default_library", "//gapis/stringtable:go_default_library", + "//gapis/vertex:go_default_library", "//tools/build/third_party/perfetto:config_go_proto", + "@com_github_golang_protobuf//jsonpb:go_default_library_gen", "@com_github_golang_protobuf//proto:go_default_library", ], ) @@ -98,12 +105,6 @@ go_stripped_binary( name = "gapit", data = [ "//cmd/gapis", - "//core/vulkan/vk_api_timing_layer/cc:libVkLayer_CPUTiming", - "//core/vulkan/vk_memory_tracker_layer/cc:libVkLayer_MemoryTracker", - "//core/vulkan/vk_virtual_swapchain/cc:json", - "//core/vulkan/vk_virtual_swapchain/cc:libVkLayer_VirtualSwapchain", - "//gapii/cc:libgapii", - "//gapii/vulkan/vk_graphics_spy/cc:json", "//gapis/messages:stb", ], embed = [":go_default_library"], diff --git a/cmd/gapit/benchmark.go b/cmd/gapit/benchmark.go index 58980995e2..3e006e8eb1 100644 --- a/cmd/gapit/benchmark.go +++ b/cmd/gapit/benchmark.go @@ -17,947 +17,1257 @@ package main import ( "bytes" "context" - "encoding/csv" - "encoding/json" + "errors" "flag" "fmt" + "hash/fnv" "io" + "math/rand" "os" - "regexp" - "strconv" "strings" "sync" - "text/tabwriter" "time" "github.com/google/gapid/core/app" "github.com/google/gapid/core/app/status" - "github.com/google/gapid/core/event/task" - img "github.com/google/gapid/core/image" + "github.com/google/gapid/core/image" "github.com/google/gapid/core/log" - "github.com/google/gapid/core/os/device" + "github.com/google/gapid/core/stream/fmts" "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/client" "github.com/google/gapid/gapis/service" "github.com/google/gapid/gapis/service/path" - "github.com/google/gapid/gapis/stringtable" + "github.com/google/gapid/gapis/vertex" ) -type benchmarkVerb struct { - BenchmarkFlags - startTime time.Time - beforeStartTraceTime time.Time - traceInitializedTime time.Time - traceDoneTime time.Time - traceSizeInBytes int64 - traceFrames int - gapisInteractiveTime time.Time - gapisCachingDoneTime time.Time - interactionStartTime time.Time - interactionDoneTime time.Time -} +var ( + actionKey = struct{}{} + thumbSize uint32 = 192 + fbRenderSettings = &path.RenderSettings{ + MaxWidth: 0xffff, + MaxHeight: 0xffff, + DrawMode: path.DrawMode_NORMAL, + } + fbHints = &path.UsageHints{Primary: true} + meshFormat = &vertex.BufferFormat{ + Streams: []*vertex.StreamFormat{ + &vertex.StreamFormat{ + Semantic: &vertex.Semantic{ + Type: vertex.Semantic_Position, + Index: 0, + }, + Format: fmts.XYZ_F32, + }, + &vertex.StreamFormat{ + Semantic: &vertex.Semantic{ + Type: vertex.Semantic_Normal, + Index: 0, + }, + Format: fmts.XYZ_F32, + }, + }, + } + defaultNumDraws = 2 +) -var BenchmarkName = "benchmark.gfxtrace" +type benchmarkVerb struct{ BenchmarkFlags } func init() { verb := &benchmarkVerb{} app.AddVerb(&app.Verb{ Name: "benchmark", - ShortHelp: "Runs a set of benchmarking tests on an application", + ShortHelp: "Runs a set of benchmarking tests on a trace", Action: verb, }) } -// We wnat to write our some of our own tracing data -type profileTask struct { - Name string `json:"name,omitempty"` - Pid uint64 `json:"pid"` - Tid uint64 `json:"tid"` - EventType string `json:"ph"` - Ts int64 `json:"ts"` - S string `json:"s,omitempty"` +func printIndices(index []uint64) string { + parts := make([]string, len(index)) + for i, v := range index { + parts[i] = fmt.Sprint(v) + } + return strings.Join(parts, ".") } -type u64List []uint64 - -// Len is the number of elements in the collection. -func (s u64List) Len() int { return len(s) } +func ignoreDataUnavailable(val interface{}, err error) (interface{}, error) { + if _, ok := err.(*service.ErrDataUnavailable); ok { + return val, nil + } + return val, err +} -// Less reports whether the element with -// index i should sort before the element with index j. -func (s u64List) Less(i, j int) bool { return s[i] < s[j] } +func newRandom(seed string, offset int64) *rand.Rand { + h := fnv.New64() + h.Write([]byte(seed)) + return rand.New(rand.NewSource(int64(h.Sum64()) + offset)) +} -// Swap swaps the elements with indexes i and j. -func (s u64List) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +type cmdTree struct { + path *path.CommandTreeNode + node *service.CommandTreeNode + cmd *api.Command + children []cmdTree +} -func (verb *benchmarkVerb) Run(ctx context.Context, flags flag.FlagSet) error { - oldCtx := ctx - ctx = status.Start(ctx, "Initializing GAPIS") +type chooser func(n int) (int, error) - if verb.NumFrames == 0 { - verb.NumFrames = 100 +func (t *cmdTree) choose(choose chooser, pred func(t *cmdTree) bool) (*cmdTree, error) { + var candidates []*cmdTree + for i := range t.children { + if pred(&t.children[i]) { + candidates = append(candidates, &t.children[i]) + } } - verb.startTime = time.Now() + if len(candidates) == 0 { + return nil, nil + } - client, err := getGapis(ctx, verb.Gapis, verb.Gapir) + idx, err := choose(len(candidates)) if err != nil { - return log.Err(ctx, err, "Failed to connect to the GAPIS server") + return nil, err } - defer func() { - if err := client.Close(); err != nil { - log.E(ctx, "Error closing client: %v", err) - } - }() - var writeTrace func(path string, gapisTrace, gapitTrace *bytes.Buffer) error + return candidates[idx], nil +} - if verb.DumpTrace != "" { - gapitTrace := &bytes.Buffer{} - gapisTrace := &bytes.Buffer{} - stopGapitTrace := status.RegisterTracer(gapitTrace) - stopGapisTrace, err := client.Profile(ctx, nil, gapisTrace, 1) - if err != nil { - return err - } +func (t *cmdTree) chooseQueueSubmit(choose chooser) (*cmdTree, error) { + submit, err := t.choose(choose, func(t *cmdTree) bool { + return t.cmd.GetName() == "vkQueueSubmit" + }) - defer func() { - stopGapitTrace() - stopGapisTrace() - // writeTrace may not be initialized yet - if writeTrace != nil { - if err := writeTrace(verb.DumpTrace, gapisTrace, gapitTrace); err != nil { - log.E(ctx, "Failed to write trace: %v", err) - } - } - }() + if err != nil { + return nil, err + } else if submit == nil { + return nil, fmt.Errorf("No submits found at node<%s>", printIndices(t.path.GetIndices())) } - stringTables, err := client.GetAvailableStringTables(ctx) + return submit, nil +} + +func (t *cmdTree) chooseSubmitInfo(choose chooser) (*cmdTree, error) { + info, err := t.choose(choose, func(t *cmdTree) bool { + return strings.HasPrefix(t.node.GetGroup(), "pSubmits[") + }) + if err != nil { - return log.Err(ctx, err, "Failed get list of string tables") + return nil, err + } else if info == nil { + return nil, fmt.Errorf("No submit infos found at node<%s>", printIndices(t.path.GetIndices())) } - var stringTable *stringtable.StringTable - if len(stringTables) > 0 { - // TODO: Let the user pick the string table. - stringTable, err = client.GetStringTable(ctx, stringTables[0]) - if err != nil { - return log.Err(ctx, err, "Failed get string table") - } - } - _ = stringTable + return info, nil +} - status.Finish(ctx) +func (t *cmdTree) chooseCommandBuffer(choose chooser) (*cmdTree, error) { + cb, err := t.choose(choose, func(t *cmdTree) bool { + return strings.HasPrefix(t.node.GetGroup(), "Command Buffer: ") + }) - if flags.NArg() > 0 { - traceURI := flags.Arg(0) - verb.doTrace(ctx, client, traceURI) - verb.traceDoneTime = time.Now() + if err != nil { + return nil, err + } else if cb == nil { + return nil, fmt.Errorf("No command buffers found at node<%s>", printIndices(t.path.GetIndices())) } - s, err := os.Stat(BenchmarkName) + return cb, nil +} + +func (t *cmdTree) chooseRenderPass(choose chooser) (*cmdTree, error) { + rp, err := t.choose(choose, func(t *cmdTree) bool { + return strings.HasPrefix(t.node.GetGroup(), "RenderPass: ") + }) + if err != nil { - return err + return nil, err + } else if rp == nil { + return nil, fmt.Errorf("No renderpass found at node<%s>", printIndices(t.path.GetIndices())) } - verb.traceSizeInBytes = s.Size() - status.Event(ctx, status.GlobalScope, "Trace Size %+v", verb.traceSizeInBytes) + return rp, nil +} + +func (t *cmdTree) chooseExecute(choose chooser) (*cmdTree, error) { + e, err := t.choose(choose, func(t *cmdTree) bool { + return t.cmd.GetName() == "vkCmdExecuteCommands" + }) - ctx = status.Start(oldCtx, "Initializing Capture") - c, err := client.LoadCapture(ctx, BenchmarkName) if err != nil { - return err + return nil, err + } else if e == nil { + return nil, fmt.Errorf("No vkCmdExecuteCommands found at node<%s>", printIndices(t.path.GetIndices())) } - devices, err := client.GetDevicesForReplay(ctx, c) + return e, nil +} + +func (t *cmdTree) chooseDrawCall(choose chooser) (*cmdTree, error) { + dc, err := t.choose(choose, func(t *cmdTree) bool { + return strings.HasPrefix(t.node.GetGroup(), "Draw") + }) + if err != nil { - panic(err) - } - if len(devices) == 0 { - panic("No devices") + return nil, err + } else if dc == nil { + return nil, fmt.Errorf("No draw calls found at node<%s>", printIndices(t.path.GetIndices())) } - resolveConfig := &path.ResolveConfig{ - ReplayDevice: devices[0], - } - device := devices[0] + return dc, nil +} - wg := sync.WaitGroup{} - gotContext := sync.WaitGroup{} +type stateTree struct { + path *path.StateTreeNode + node *service.StateTreeNode + children []stateTree +} - var resources *service.Resources - wg.Add(1) - go func() { - ctx := status.Start(oldCtx, "Resolving Resources") - defer status.Finish(ctx) - boxedResources, err := client.Get(ctx, c.Resources().Path(), resolveConfig) - if err != nil { - panic(err) - } - resources = boxedResources.(*service.Resources) +type measurement struct { + name string + duration time.Duration + children []*measurement +} - wg.Done() - }() +type drawSummary struct { + find time.Duration + selection time.Duration + framebuffer time.Duration +} - var context *service.Context - var ctxId *path.ID +type summary struct { + init time.Duration + initStart time.Duration + initLoad time.Duration + initLoadProfile time.Duration + draws []drawSummary +} - wg.Add(1) - gotContext.Add(1) - go func() { - ctx := status.Start(oldCtx, "Resolving Contexts") - defer status.Finish(ctx) - contextsInterface, err := client.Get(ctx, c.Contexts().Path(), resolveConfig) - if err != nil { - panic(err) +func (s *summary) print(w io.Writer) error { + if _, err := fmt.Fprint(w, "Init,Init.Start,Init.Load,Init.Load.Profile"); err != nil { + return err + } + for i := range s.draws { + if _, err := fmt.Fprintf(w, ",Draw%d.Find,Draw%d.Select,Draw%d.Select.Framebuffer", i, i, i); err != nil { + return err } - contexts := contextsInterface.(*service.Contexts) - ctxId = contexts.GetList()[0].ID - contextInterface, err := client.Get(ctx, contexts.GetList()[0].Path(), resolveConfig) - context = contextInterface.(*service.Context) + } + if _, err := fmt.Fprintln(w); err != nil { + return err + } - gotContext.Done() - wg.Done() - }() + if _, err := fmt.Fprintf(w, "%0.3f,%0.3f,%0.3f,%0.3f", s.init.Seconds(), s.initStart.Seconds(), s.initLoad.Seconds(), s.initLoadProfile.Seconds()); err != nil { + return err + } + for _, draw := range s.draws { + if _, err := fmt.Fprintf(w, ",%0.3f,%0.3f,%0.3f", draw.find.Seconds(), draw.selection.Seconds(), draw.framebuffer.Seconds()); err != nil { + return err + } + } + if _, err := fmt.Fprintln(w); err != nil { + return err + } + return nil +} - wg.Add(1) - go func() { - ctx := status.Start(oldCtx, "Getting Report") - defer status.Finish(ctx) - gotContext.Wait() - filter := &path.CommandFilter{} - filter.Context = ctxId +func (m *measurement) getDuration() time.Duration { + if m == nil { + return time.Duration(0) + } + return m.duration +} - _, err := client.Get(ctx, c.Commands().Path(), resolveConfig) - if err != nil { - panic(err) +func (m *measurement) computeSummary() summary { + r := summary{ + init: m.find("init").getDuration(), + initStart: m.find("init", "start_gapis").getDuration(), + initLoad: m.find("init", "load").getDuration(), + initLoadProfile: m.find("init", "load", "profile").getDuration(), + } + + for _, child := range m.children { + if child.name == "find_and_select_command" { + r.draws = append(r.draws, drawSummary{ + find: child.find("find_draw").getDuration(), + selection: child.find("select_command").getDuration(), + framebuffer: child.find("select_command", "framebuffer").getDuration(), + }) } + } - _, err = client.Get(ctx, c.Report(device, filter, false).Path(), resolveConfig) - wg.Done() - }() + return r +} + +func (m *measurement) find(path ...string) *measurement { + if len(path) == 0 { + return m + } - var commandToClick *path.Command - - wg.Add(1) - - var events []*service.Event - go func() { - ctx := status.Start(oldCtx, "Getting Thumbnails") - defer status.Finish(ctx) - var e error - events, e = getEvents(ctx, client, &path.Events{ - Capture: c, - AllCommands: false, - FirstInFrame: false, - LastInFrame: true, - FramebufferObservations: false, - IncludeTiming: true, - }) - if e != nil { - panic(e) + for _, child := range m.children { + if child.name == path[0] || strings.HasPrefix(child.name, path[0]+"<") { + if r := child.find(path[1:]...); r != nil { + return r + } } - verb.traceFrames = len(events) - - gotThumbnails := sync.WaitGroup{} - //Get thumbnails - settings := &service.RenderSettings{MaxWidth: uint32(256), MaxHeight: uint32(256)} - numThumbnails := 10 - if len(events) < 10 { - numThumbnails = len(events) + } + return nil +} + +func (m *measurement) writeCsv(w io.Writer) error { + for _, child := range m.children { + if err := child.writeCsvNode(w, ""); err != nil { + return err } - commandToClick = events[len(events)-1].Command - for i := len(events) - numThumbnails; i < len(events); i++ { - gotThumbnails.Add(1) - hints := &service.UsageHints{Preview: true} - go func(i int) { - iip, err := client.GetFramebufferAttachment(ctx, - &service.ReplaySettings{ - Device: device, - DisableReplayOptimization: verb.NoOpt, - DisplayToSurface: false, - }, - events[i].Command, api.FramebufferAttachment_Color0, settings, hints) + } + return nil +} - iio, err := client.Get(ctx, iip.Path(), resolveConfig) - if err != nil { - panic(log.Errf(ctx, err, "Get frame image.Info failed")) - } - ii := iio.(*img.Info) - dataO, err := client.Get(ctx, path.NewBlob(ii.Bytes.ID()).Path(), resolveConfig) - if err != nil { - panic(log.Errf(ctx, err, "Get frame image data failed")) - } - _, _, _ = int(ii.Width), int(ii.Height), dataO.([]byte) - gotThumbnails.Done() - }(i) +func (m *measurement) writeCsvNode(w io.Writer, parent string) error { + name := parent + m.name + if _, err := fmt.Fprintf(w, "\"%s\",%v\n", name, m.duration); err != nil { + return err + } + for _, child := range m.children { + if err := child.writeCsvNode(w, name+"."); err != nil { + return err } - gotThumbnails.Wait() - wg.Done() - }() + } + return nil +} + +func (m *measurement) writeGraph(w io.Writer) error { + if _, err := fmt.Fprintln(w, "digraph Profile {"); err != nil { + return err + } - wg.Add(1) - go func() { - ctx := status.Start(oldCtx, "Resolving Command Tree") - - gotContext.Wait() - filter := &path.CommandFilter{} - filter.Context = ctxId - - treePath := c.CommandTree(filter) - treePath.GroupByApi = true - treePath.GroupByContext = true - treePath.GroupByDrawCall = true - treePath.GroupByFrame = true - treePath.GroupByUserMarkers = true - treePath.GroupBySubmission = true - treePath.IncludeNoContextGroups = true - treePath.AllowIncompleteFrame = true - treePath.MaxChildren = int32(2000) - - boxedTree, err := client.Get(ctx, treePath.Path(), resolveConfig) + id := 1 + var err error + for _, child := range m.children { + id, err = child.writeGraphNode(w, id) if err != nil { - panic(log.Err(ctx, err, "Failed to load the command tree")) + return err } - tree := boxedTree.(*service.CommandTree) + } + _, err = fmt.Fprintln(w, "}") + return err +} - boxedNode, err := client.Get(ctx, tree.Root.Path(), resolveConfig) +func (m *measurement) writeGraphNode(w io.Writer, id int) (int, error) { + if _, err := fmt.Fprintf(w, "%d [label=\"%s|%v\"];\n", id, m.name, m.duration); err != nil { + return 0, err + } + + cur := id + 1 + for _, child := range m.children { + next, err := child.writeGraphNode(w, cur) if err != nil { - panic(log.Errf(ctx, err, "Failed to load the node at: %v", tree.Root.Path())) + return 0, err } - - n := boxedNode.(*service.CommandTreeNode) - numChildren := 30 - if n.NumChildren < 30 { - numChildren = int(n.NumChildren) + if _, err := fmt.Fprintf(w, "%d -> %d;\n", id, cur); err != nil { + return 0, err } - gotThumbnails := sync.WaitGroup{} - gotNodes := sync.WaitGroup{} - settings := &service.RenderSettings{MaxWidth: uint32(64), MaxHeight: uint32(64)} - hints := &service.UsageHints{Background: true} - tnCtx := status.Start(oldCtx, "Resolving Command Thumbnails") - for i := 0; i < numChildren; i++ { - gotThumbnails.Add(1) - gotNodes.Add(1) - go func(i int) { - defer gotThumbnails.Done() - boxedChild, err := client.Get(ctx, tree.Root.Child(uint64(i)).Path(), resolveConfig) - if err != nil { - panic(err) - } - child := boxedChild.(*service.CommandTreeNode) - gotNodes.Done() - iip, err := client.GetFramebufferAttachment(tnCtx, - &service.ReplaySettings{ - Device: device, - DisableReplayOptimization: verb.NoOpt, - DisplayToSurface: false, - }, - child.Representation, api.FramebufferAttachment_Color0, settings, hints) + cur = next + } + return cur, nil +} - iio, err := client.Get(tnCtx, iip.Path(), resolveConfig) - if err != nil { - return - } - ii := iio.(*img.Info) - dataO, err := client.Get(tnCtx, path.NewBlob(ii.Bytes.ID()).Path(), resolveConfig) - if err != nil { - panic(log.Errf(tnCtx, err, "Get frame image data failed")) +type benchmark struct { + rnd *rand.Rand + + client client.Client + capture *path.Capture + device *path.Device + cmdTree *cmdTree + resources *service.Resources + + measurement measurement + mutex sync.Mutex + + gapitTrace bytes.Buffer + gapisTrace bytes.Buffer + stopGapitTrace status.Unregister + stopGapisTrace func() error +} + +type action interface { + name() string + exec(ctx context.Context, b *benchmark) (interface{}, error) + cleanup(ctx context.Context, b *benchmark, err error) +} + +func (b *benchmark) measure(ctx context.Context, a action) (interface{}, error) { + return b.measureFun(ctx, a.name(), func(ctx context.Context) (interface{}, error) { + return a.exec(ctx, b) + }) +} + +func (b *benchmark) measureFun(ctx context.Context, name string, f func(ctx context.Context) (interface{}, error)) (interface{}, error) { + me := &measurement{name: name} + + var parent *measurement + parentValue := ctx.Value(actionKey) + if parentValue == nil { + parent = &b.measurement + } else { + parent = parentValue.(*measurement) + } + + b.mutex.Lock() + parent.children = append(parent.children, me) + b.mutex.Unlock() + + ctx = context.WithValue(ctx, actionKey, me) + + ctx = status.Start(ctx, name) + defer status.Finish(ctx) + + start := time.Now() + defer func() { + me.duration = time.Since(start) + }() + + return f(ctx) +} + +func (b *benchmark) resolveConfig() *path.ResolveConfig { + return &path.ResolveConfig{ + ReplayDevice: b.device, + } +} + +func (b *benchmark) resourcesByType(after *path.Command, ty path.ResourceType) []*service.Resource { + var res []*service.Resource + for _, byType := range b.resources.GetTypes() { + if byType.GetType() == ty { + for _, resource := range byType.GetResources() { + count := len(resource.Accesses) + if count == 0 || !resource.Accesses[0].IsAfter(after) { + res = append(res, resource) } - _, _, _ = int(ii.Width), int(ii.Height), dataO.([]byte) - }(i) + } } + } + return res +} - gotNodes.Wait() - status.Finish(ctx) - verb.gapisInteractiveTime = time.Now() +func (b *benchmark) textures(after *path.Command) []*service.Resource { + return b.resourcesByType(after, path.ResourceType_Texture) +} + +func (b *benchmark) shaders(after *path.Command) []*service.Resource { + return b.resourcesByType(after, path.ResourceType_Shader) +} + +type parallel struct { + label string + actions []action +} + +func (p *parallel) name() string { + return p.label +} + +func (p *parallel) exec(ctx context.Context, b *benchmark) (interface{}, error) { + var wg sync.WaitGroup + errors := make([]error, len(p.actions)) + + for i := range p.actions { + wg.Add(1) + go func(i int) { + defer wg.Done() + ctx := status.PutTask(ctx, nil) // Clear the parent, since we're parallel + _, err := b.measure(ctx, p.actions[i]) + errors[i] = err + }(i) + } - gotThumbnails.Wait() - status.Finish(tnCtx) - wg.Done() - }() - // Done initializing capture wg.Wait() - verb.gapisCachingDoneTime = time.Now() - - // At this point we are Interactive. All pre-loading is done: - // Next we have to actually handle an interaction - status.Finish(ctx) - - status.Event(ctx, status.GlobalScope, "Load done, interaction starting %+v", verb.traceSizeInBytes) - - // Sleep for 20 seconds so that the server is idle before we do - // the last part of the benchmark. When we open a trace we, in the - // background, generate the Dependency Graph. If we start making - // requests before that is done, we will skew the benchmarking - // results for 2 reasons: - // - // 1. Because the CPU will be under load for building the Dep - // graph - // - // 2. Because requests that normally use the dep graph (getting - // the framebuffer observations in this case) won't take - // advantage of it. - time.Sleep(20 * time.Second) - - ctx = status.Start(oldCtx, "Interacting with frame") - // One interaction done - verb.interactionStartTime = time.Now() - - interactionWG := sync.WaitGroup{} - // Get the framebuffer - interactionWG.Add(1) - go func() { - ctx = status.Start(oldCtx, "Getting Framebuffer") - defer status.Finish(ctx) - defer interactionWG.Done() - hints := &service.UsageHints{Primary: true} - settings := &service.RenderSettings{MaxWidth: uint32(0xFFFFFFFF), MaxHeight: uint32(0xFFFFFFFF)} - iip, err := client.GetFramebufferAttachment(ctx, - &service.ReplaySettings{ - Device: device, - DisableReplayOptimization: verb.NoOpt, - DisplayToSurface: false, - }, - commandToClick, api.FramebufferAttachment_Color0, settings, hints) - iio, err := client.Get(ctx, iip.Path(), resolveConfig) + // TODO: combine errors, rather than returning the first non-nil error. + for _, err := range errors { if err != nil { - return + return nil, err } - ii := iio.(*img.Info) - dataO, err := client.Get(ctx, path.NewBlob(ii.Bytes.ID()).Path(), resolveConfig) - if err != nil { - panic(log.Errf(ctx, err, "Get frame image data failed")) - } - _, _, _ = int(ii.Width), int(ii.Height), dataO.([]byte) - }() + } + return nil, nil +} - // Get state tree - interactionWG.Add(1) - go func() { - ctx = status.Start(oldCtx, "Resolving State Tree") - defer status.Finish(ctx) - defer interactionWG.Done() - //commandToClick - boxedTree, err := client.Get(ctx, commandToClick.StateAfter().Tree().Path(), resolveConfig) - if err != nil { - panic(log.Err(ctx, err, "Failed to load the state tree")) +func (p *parallel) cleanup(ctx context.Context, b *benchmark, err error) { + for _, a := range p.actions { + a.cleanup(ctx, b, err) + } +} + +type sequential struct { + label string + actions []action +} + +func (s *sequential) name() string { + return s.label +} + +func (s *sequential) exec(ctx context.Context, b *benchmark) (interface{}, error) { + for _, a := range s.actions { + if _, err := b.measure(ctx, a); err != nil { + return nil, err } - tree := boxedTree.(*service.StateTree) + } + return nil, nil +} + +func (s *sequential) cleanup(ctx context.Context, b *benchmark, err error) { + for i := len(s.actions) - 1; i >= 0; i-- { + s.actions[i].cleanup(ctx, b, err) + } +} + +type actionFun func(ctx context.Context, b *benchmark) (interface{}, error) + +type simple struct { + label string + fun actionFun +} - boxedRoot, err := client.Get(ctx, tree.Root.Path(), resolveConfig) +func (s *simple) name() string { + return s.label +} + +func (s *simple) exec(ctx context.Context, b *benchmark) (interface{}, error) { + return s.fun(ctx, b) +} + +func (s *simple) cleanup(ctx context.Context, b *benchmark, err error) { +} + +type startGapis struct { + gapisFlags *GapisFlags + gapirFlags *GapirFlags + dumpTrace bool +} + +func (a *startGapis) name() string { + return "start_gapis" +} + +func (a *startGapis) exec(ctx context.Context, b *benchmark) (interface{}, error) { + var err error + if b.client, err = getGapis(ctx, *a.gapisFlags, *a.gapirFlags); err != nil { + return nil, err + } + + if a.dumpTrace { + b.stopGapisTrace, err = b.client.Profile(ctx, nil, &b.gapisTrace, 1) if err != nil { - panic(log.Err(ctx, err, "Failed to load the state tree")) + return nil, err } - root := boxedRoot.(*service.StateTreeNode) + } + + _, err = b.measureFun(ctx, "server_info", func(ctx context.Context) (interface{}, error) { + return b.client.GetServerInfo(ctx) + }) + if err != nil { + return nil, err + } - gotNodes := sync.WaitGroup{} - numChildren := 30 - if root.NumChildren < 30 { - numChildren = int(root.NumChildren) + _, err = b.measureFun(ctx, "string_table", func(ctx context.Context) (interface{}, error) { + tables, err := b.client.GetAvailableStringTables(ctx) + if len(tables) > 0 { + _, err = b.client.GetStringTable(ctx, tables[0]) } - for i := 0; i < numChildren; i++ { - gotNodes.Add(1) - go func(i int) { - defer gotNodes.Done() - boxedChild, err := client.Get(ctx, tree.Root.Index(uint64(i)).Path(), resolveConfig) + return nil, err + }) + + return nil, err +} + +func (a *startGapis) cleanup(ctx context.Context, b *benchmark, err error) { + if b.stopGapisTrace != nil { + b.stopGapisTrace() + } + if b.client != nil { + b.client.Close() + } +} + +func loadCapture(file string) action { + return &simple{ + "load_capture", + func(ctx context.Context, b *benchmark) (ignored interface{}, err error) { + b.capture, err = b.client.LoadCapture(ctx, file) + if err == nil { + var c interface{} + c, err = b.client.Get(ctx, b.capture.Path(), &path.ResolveConfig{}) + if err == nil && c.(*service.Capture).GetType() != service.TraceType_Graphics { + err = errors.New("Not a graphics capture") + } + } + return + }, + } +} + +func loadReplaydevice() action { + return &simple{ + "load_replay_device", + func(ctx context.Context, b *benchmark) (interface{}, error) { + devices, compat, _, err := b.client.GetDevicesForReplay(ctx, b.capture) + if err != nil { + return nil, err + } + + if len(devices) == 0 || !compat[0] { + return nil, errors.New("No compatible replay device attached") + } + b.device = devices[0] + return devices[0], nil + }, + } +} + +func loadResources() action { + return &simple{ + "resources", + func(ctx context.Context, b *benchmark) (interface{}, error) { + res, err := b.client.Get(ctx, b.capture.Resources().Path(), b.resolveConfig()) + if err != nil { + return nil, err + } + b.resources = res.(*service.Resources) + return res, err + }, + } +} + +func loadCommandTree() action { + return &simple{ + "cmd_tree", + func(ctx context.Context, b *benchmark) (interface{}, error) { + tree, err := b.client.Get(ctx, (&path.CommandTree{ + Capture: b.capture, + GroupByFrame: true, + GroupByDrawCall: true, + GroupByTransformFeedback: true, + GroupByUserMarkers: true, + GroupBySubmission: true, + AllowIncompleteFrame: true, + MaxChildren: 2000, + MaxNeighbours: 20, + }).Path(), b.resolveConfig()) + if err != nil { + return nil, err + } + b.cmdTree = &cmdTree{ + path: tree.(*service.CommandTree).GetRoot(), + } + + if _, err := b.measure(ctx, loadCommandTreeNode(b.cmdTree)); err != nil { + return nil, err + } + + return b.measure(ctx, expandCommandTreeNode(b.cmdTree)) + }, + } +} + +func loadCommandTreeThumbnail(node *path.CommandTreeNode) action { + return &simple{ + "cmd_tree_thumbnail", + func(ctx context.Context, b *benchmark) (interface{}, error) { + info, err := ignoreDataUnavailable(b.client.Get(ctx, (&path.Thumbnail{ + Object: &path.Thumbnail_CommandTreeNode{CommandTreeNode: node}, + DesiredFormat: image.RGBA_U8_NORM, + DesiredMaxWidth: thumbSize, + DesiredMaxHeight: thumbSize, + }).Path(), b.resolveConfig())) + if err != nil || info == nil { + return nil, err + } + + return ignoreDataUnavailable( + b.client.Get(ctx, path.NewBlob(info.(*image.Info).GetBytes().ID()).Path(), b.resolveConfig())) + }, + } +} + +func loadCommand(cmd *path.Command) action { + return &simple{ + "command<" + printIndices(cmd.GetIndices()) + ">", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, cmd.Path(), b.resolveConfig()) + }, + } +} + +func loadCommandTreeNode(tree *cmdTree) action { + return &simple{ + "cmd_tree_node<" + printIndices(tree.path.GetIndices()) + ">", + func(ctx context.Context, b *benchmark) (interface{}, error) { + node, err := b.client.Get(ctx, tree.path.Path(), b.resolveConfig()) + if err != nil { + return nil, err + } + tree.node = node.(*service.CommandTreeNode) + + if tree.node.GetGroup() != "" { + _, err = b.measure(ctx, loadCommandTreeThumbnail(tree.path)) + } else if tree.node.GetCommands() != nil { // group is empty + cmd, err := b.measure(ctx, loadCommand(tree.node.GetCommands().Last())) if err != nil { - panic(err) + return nil, err } - child := boxedChild.(*service.StateTreeNode) + tree.cmd = cmd.(*api.Command) + } + + return node, err + }, + } +} + +func expandCommandTreeNode(tree *cmdTree) action { + actions := make([]action, tree.node.GetNumChildren()) + tree.children = make([]cmdTree, tree.node.GetNumChildren()) + for i := range actions { + tree.children[i].path = tree.path.Child(uint64(i)) + actions[i] = loadCommandTreeNode(&tree.children[i]) + } + return ¶llel{ + "cmd_tree_expand<" + printIndices(tree.path.GetIndices()) + ">", + actions, + } +} - if child.Preview != nil { - if child.Constants != nil { - _, _ = getConstantSet(ctx, client, child.Constants) +func loadProfilingData() action { + return &simple{ + "profile", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.GpuProfile(ctx, &service.GpuProfileRequest{ + Capture: b.capture, + Device: b.device, + }) + }, + } +} + +func findDrawCall(path []uint64, secondary bool) action { + return &simple{ + "find_draw", + func(ctx context.Context, b *benchmark) (interface{}, error) { + choose := func(name string, idx int) chooser { + if idx < len(path) { + return func(count int) (int, error) { + v := int(path[idx]) + if v >= count { + return 0, fmt.Errorf("Invalid %s index provided in path %v: %d >= %d", name, path, v, count) + } + return v, nil + } + } else { + return func(count int) (int, error) { + return b.rnd.Intn(count), nil } } - }(i) - } - gotNodes.Wait() - }() + } - // Get the mesh - interactionWG.Add(1) - go func() { - ctx = status.Start(oldCtx, "Getting Mesh") - defer status.Finish(ctx) - defer interactionWG.Done() - meshOptions := path.NewMeshOptions(false) - _, _ = client.Get(ctx, commandToClick.Mesh(meshOptions).Path(), resolveConfig) - }() + submit, err := b.cmdTree.chooseQueueSubmit(choose("queue submit", 0)) + if err != nil { + return nil, err + } + if submit.children == nil { + if _, err := b.measure(ctx, expandCommandTreeNode(submit)); err != nil { + return nil, err + } + } - // GetMemory - interactionWG.Add(1) - go func() { - ctx = status.Start(oldCtx, "Getting Memory") - defer status.Finish(ctx) - defer interactionWG.Done() - observationsPath := &path.Memory{ - Address: 0, - Size: uint64(0xFFFFFFFFFFFFFFFF), - Pool: 0, - After: commandToClick, - ExcludeData: true, - ExcludeObserved: true, - } - allMemory, err := client.Get(ctx, observationsPath.Path(), resolveConfig) - if err != nil { - panic(err) - } - memory := allMemory.(*service.Memory) - var mem *service.MemoryRange - if len(memory.Reads) > 0 { - mem = memory.Reads[0] - } else if len(memory.Writes) > 0 { - mem = memory.Writes[0] - } else { - log.I(ctx, "No memory observations.") - return - } - client.Get(ctx, commandToClick.MemoryAfter(0, mem.Base, 64*1024).Path(), resolveConfig) - }() + info, err := submit.chooseSubmitInfo(choose("submit info", 1)) + if err != nil { + return nil, err + } + if info.children == nil { + if _, err := b.measure(ctx, expandCommandTreeNode(info)); err != nil { + return nil, err + } + } - // Get Resource Data (For each texture, and shader) - interactionWG.Add(1) - go func() { - ctx = status.Start(oldCtx, "Getting Resources") - defer status.Finish(ctx) - defer interactionWG.Done() - gotResources := sync.WaitGroup{} - for _, types := range resources.GetTypes() { - for ii, v := range types.GetResources() { - if (types.Type == api.ResourceType_TextureResource || - types.Type == api.ResourceType_ShaderResource || - types.Type == api.ResourceType_ProgramResource) && - ii < 30 { - gotResources.Add(1) - go func(id *path.ID) { - defer gotResources.Done() - resourcePath := commandToClick.ResourceAfter(id) - _, _ = client.Get(ctx, resourcePath.Path(), resolveConfig) - }(v.ID) + cb, err := info.chooseCommandBuffer(choose("command buffer", 2)) + if err != nil { + return nil, err + } + if cb.children == nil { + if _, err := b.measure(ctx, expandCommandTreeNode(cb)); err != nil { + return nil, err } } - } - gotResources.Wait() - }() - interactionWG.Wait() - verb.interactionDoneTime = time.Now() - status.Finish(ctx) + rp, err := cb.chooseRenderPass(choose("renderpass", 3)) + if err != nil { + return nil, err + } + if rp.children == nil { + if _, err := b.measure(ctx, expandCommandTreeNode(rp)); err != nil { + return nil, err + } + } - m, err := client.Get(ctx, c.Messages().Path(), nil) - if err != nil { - return err + level := 4 + parent := rp + if secondary { + level = 6 + exec, err := rp.chooseExecute(choose("execute", 4)) + if err != nil { + return nil, err + } + if exec.children == nil { + if _, err := b.measure(ctx, expandCommandTreeNode(exec)); err != nil { + return nil, err + } + } + + parent, err = exec.chooseCommandBuffer(choose("secondary command buffer", 5)) + if err != nil { + return nil, err + } + if parent.children == nil { + if _, err := b.measure(ctx, expandCommandTreeNode(parent)); err != nil { + return nil, err + } + } + } + + return parent.chooseDrawCall(choose("draw call", level)) + }, } - messages := m.(*service.Messages) +} - boxedVal, err := client.Get(ctx, (&path.Stats{ - Capture: c, - DrawCall: false, - }).Path(), nil) - if err != nil { - return err +func loadAttachments(after *path.Command) action { + return &simple{ + "attachments", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, (&path.FramebufferAttachments{ + After: after, + }).Path(), b.resolveConfig()) + }, } - traceStartTimestamp := boxedVal.(*service.Stats).TraceStart - - frameTimes := []uint64{} - - stateBuildTime := int64(0) - stateBuildStartTime := traceStartTimestamp - stateBuildEndTime := traceStartTimestamp - hasStateSerialization := false - frameRe := regexp.MustCompile("Frame Number: [\\d]*") - for _, m := range messages.List { - if m.Message == "State serialization started" { - hasStateSerialization = true - stateBuildStartTime = m.Timestamp - } else if m.Message == "State serialization finished" { - stateBuildEndTime = m.Timestamp - stateBuildTime = int64(stateBuildEndTime - stateBuildStartTime) - } else if !hasStateSerialization && frameRe.MatchString(m.Message) { - frameTimes = append(frameTimes, m.Timestamp) - } +} + +func loadAttachment(after *path.Command, index uint32) action { + return &simple{ + fmt.Sprintf("attachment<%d>", index), + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, (&path.FramebufferAttachment{ + After: after, + Index: index, + RenderSettings: fbRenderSettings, + Hints: fbHints, + }).Path(), b.resolveConfig()) + }, } +} + +func loadFrameBuffer(after *path.Command) action { + return &simple{ + "framebuffer", + func(ctx context.Context, b *benchmark) (interface{}, error) { + attsBoxed, err := b.measure(ctx, loadAttachments(after)) + if err != nil { + return nil, err + } + + atts := attsBoxed.(*service.FramebufferAttachments).GetAttachments() + if len(atts) == 0 { + return nil, nil + } + + attBoxed, err := b.measure(ctx, loadAttachment(after, atts[0].GetIndex())) + if err != nil { + return nil, err + } + infoPath := attBoxed.(*service.FramebufferAttachment).GetImageInfo() + + infoBoxed, err := b.client.Get(ctx, infoPath.Path(), b.resolveConfig()) + if err != nil { + return nil, err + } - if len(events) < 1 { - panic("No events") + return b.client.Get(ctx, path.NewBlob(infoBoxed.(*image.Info).GetBytes().ID()).Path(), b.resolveConfig()) + }, } - lastFrameEvent := events[len(events)-1] - frameCaptureTime := lastFrameEvent.Timestamp - stateBuildEndTime - // Convert nanoseconds to milliseconds - frameTime := float64(frameCaptureTime / uint64(len(events))) - stateTime := float64(stateBuildTime) - traceMaxMemory := int64(0) +} - nonLoadingFrameTime := uint64(0) - // We assume that the last 20% of frames come from a non-loading screen - if hasStateSerialization { - nFrames := len(frameTimes) / 5 - stableStart := frameTimes[len(frameTimes)-nFrames-1] - stableEnd := frameTimes[len(frameTimes)-1] - nonLoadingFrameTime = (stableEnd - stableStart) / uint64(nFrames) +func loadTextureThumbnail(texture *service.Resource, info *image.Info) action { + return &simple{ + fmt.Sprintf("texture_thumbnail<%v>", texture.Handle), + func(ctx context.Context, b *benchmark) (interface{}, error) { + return ignoreDataUnavailable( + b.client.Get(ctx, path.NewBlob(info.GetBytes().ID()).Path(), b.resolveConfig())) + }, } +} - ctx = oldCtx - writeOutput := func() { - preMecFramerate := float64(stateBuildStartTime - traceStartTimestamp) - if verb.StartFrame > 0 { - preMecFramerate = preMecFramerate / float64(verb.StartFrame) - } - if verb.OutputCSV { - csvWriter := csv.NewWriter(os.Stdout) - header := []string{ - "Trace Time (ms)", "Trace Size", "Trace Frames", "State Serialization (ms)", "Trace Frame Time (ms)", "Interactive (ms)", - "Caching Done (ms)", "Interaction (ms)", "Max Memory", "Before MEC Frame Time (ms)", "Trailing Frame Time (ms)"} - csvWriter.Write(header) - record := []string{ - fmt.Sprint(float64(verb.traceDoneTime.Sub(verb.beforeStartTraceTime).Nanoseconds()) / float64(time.Millisecond)), - fmt.Sprint(verb.traceSizeInBytes), - fmt.Sprint(verb.traceFrames), - fmt.Sprint(stateTime / float64(time.Millisecond)), - fmt.Sprint(frameTime / float64(time.Millisecond)), - fmt.Sprint(float64(verb.gapisInteractiveTime.Sub(verb.traceDoneTime).Nanoseconds()) / float64(time.Millisecond)), - fmt.Sprint(float64(verb.gapisCachingDoneTime.Sub(verb.traceDoneTime).Nanoseconds()) / float64(time.Millisecond)), - fmt.Sprint(float64(verb.interactionDoneTime.Sub(verb.interactionStartTime).Nanoseconds()) / float64(time.Millisecond)), - fmt.Sprint(traceMaxMemory), - fmt.Sprint(preMecFramerate / float64(time.Millisecond)), - fmt.Sprint(float64(nonLoadingFrameTime) / float64(time.Millisecond)), - } - csvWriter.Write(record) - csvWriter.Flush() - } else { - w := tabwriter.NewWriter(os.Stdout, 4, 4, 3, ' ', 0) - fmt.Fprintln(w, "Trace Time\tTrace Size\tTrace Frames\tState Serialization\tTrace Frame Time\tInteractive") - fmt.Fprintln(w, "----------\t----------\t------------\t-------------------\t----------------\t-----------") - fmt.Fprintf(w, "%v\t%v\t%v\t%v\t%v\t%v\n", - verb.traceDoneTime.Sub(verb.beforeStartTraceTime), - verb.traceSizeInBytes, - verb.traceFrames, - time.Duration(stateTime)*time.Nanosecond, - time.Duration(frameTime)*time.Nanosecond, - verb.gapisInteractiveTime.Sub(verb.traceDoneTime), - ) - w.Flush() - fmt.Fprintln(os.Stdout, "") - w = tabwriter.NewWriter(os.Stdout, 4, 4, 3, ' ', 0) - fmt.Fprintln(w, "Caching Done\tInteraction\tMax Memory\tBefore MEC Frame Time\tTrailing Frame Time") - fmt.Fprintln(w, "------------\t-----------\t----------\t---------------------\t-----------------") - fmt.Fprintf(w, "%+v\t%+v\t%+v\t%+v\t%+v\n", - verb.gapisCachingDoneTime.Sub(verb.traceDoneTime), - verb.interactionDoneTime.Sub(verb.interactionStartTime), - traceMaxMemory, - time.Duration(preMecFramerate)*time.Nanosecond, - time.Duration(nonLoadingFrameTime)*time.Nanosecond, - ) - w.Flush() - } +func loadTextureThumbnailInfos(after *path.Command) action { + data := after.AllResourcesAfter(path.ResourceType_Texture) + return &simple{ + "tumbnail_infos", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, (&path.Thumbnail{ + Object: &path.Thumbnail_Resources{Resources: data}, + DesiredFormat: image.RGBA_U8_NORM, + DesiredMaxWidth: thumbSize, + DesiredMaxHeight: thumbSize, + }).Path(), b.resolveConfig()) + }, } +} - writeTrace = func(path string, gapisTrace, gapitTrace *bytes.Buffer) error { - f, err := os.Create(path) - if err != nil { - return err - } - defer f.Close() +func loadTextureThumbnails(after *path.Command) action { + return &simple{ + "texture_thumbnails", + func(ctx context.Context, b *benchmark) (interface{}, error) { + infosBoxed, err := b.measure(ctx, loadTextureThumbnailInfos(after)) + if err != nil { + return nil, err + } + infos := infosBoxed.(*service.MultiResourceThumbnail) + + textures := b.textures(after) + textureCount := len(textures) + // The UI only loads the thumbnails of the visible textures, which is typically + // around 20 images. We are conservative and pretend the view is huge and load 50. + if textureCount > 50 { + textureCount = 50 + } - _, err = f.Write(gapisTrace.Bytes()) - if err != nil { - return err - } - // Skip the leading [ - _, err = f.Write(gapitTrace.Bytes()[1:]) - if err != nil { - return err - } - // This is the entire profile except for what happened on the trace device. - // This is now stored in the trace file. - // We have all of the timing information for the trace file, - // the last thing we have to do is sync the existing traces with our trace. - // We need to find the point in the GAPIS trace where the trace was connected. - timeOffsetInMicroseconds := int64(0) - var prof interface{} - err = json.Unmarshal([]byte(string(gapisTrace.Bytes()[:len(gapisTrace.Bytes())-1])+"]"), &prof) - if prof, ok := prof.([]interface{}); ok { - for _, d := range prof { - if d, ok := d.(map[string]interface{}); ok { - if n, ok := d["name"]; ok { - if s, ok := n.(string); ok { - if s == "Trace Connected" { - if n, ok := d["ts"]; ok { - if n, ok := n.(float64); ok { - timeOffsetInMicroseconds = int64(n) - } - } - } else if s == "periodic_interval" { - d := d["args"].(map[string]interface{}) - d = d["dumps"].(map[string]interface{}) - d = d["process_totals"].(map[string]interface{}) - m := d["heap_in_use"].(string) - b, _ := strconv.ParseInt("0x"+m, 0, 64) - if b > traceMaxMemory { - traceMaxMemory = b - } - } - } + thumbs := make([]action, 0, textureCount) + for i := 0; i < textureCount; i++ { + if info, ok := infos.Images[textures[i].ID.ID().String()]; ok { + if info := info.GetImage(); info != nil { + thumbs = append(thumbs, loadTextureThumbnail(textures[i], info)) } } } - } else { - panic(fmt.Sprintf("Could not read profile data: %+v", err)) - } - traceStartTimestampInMicroseconds := (traceStartTimestamp / 1000) - timeOffsetInMicroseconds = int64(traceStartTimestampInMicroseconds) - timeOffsetInMicroseconds - // Manually write out some profiling data for the trace - tsk := profileTask{ - Name: "Tracing", - Tid: 0, - Pid: 1, - Ts: int64(traceStartTimestampInMicroseconds) - timeOffsetInMicroseconds, - EventType: "B", - } - b, _ := json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - - startTime := traceStartTimestampInMicroseconds - for i, m := range frameTimes { - if m >= stateBuildStartTime { - break - } - tsk.Name = fmt.Sprintf("Untracked Frame %+v", i) - tsk.Ts = int64(startTime) - timeOffsetInMicroseconds - tsk.EventType = "B" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - - tsk.Name = "" - tsk.Ts = int64(m/1000) - timeOffsetInMicroseconds - tsk.EventType = "E" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - - startTime = (m / 1000) - } + return b.measure(ctx, ¶llel{ + label: "images", + actions: thumbs, + }) + }, + } +} - if stateBuildStartTime != stateBuildEndTime { - tsk.Name = "State Serialization" - tsk.Ts = int64(stateBuildStartTime/1000) - timeOffsetInMicroseconds - tsk.EventType = "B" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - - tsk.Name = "" - tsk.Ts = int64(stateBuildEndTime/1000) - timeOffsetInMicroseconds - tsk.EventType = "E" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - } +func loadTextureDatas(after *path.Command) action { + data := after.AllResourcesAfter(path.ResourceType_Texture) + return &simple{ + "texture_resource_datas", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, data.Path(), b.resolveConfig()) + }, + } +} - startTime = (stateBuildEndTime / 1000) - for i, e := range events { - tsk.Name = fmt.Sprintf("Frame %+v", i) - tsk.Ts = int64(startTime) - timeOffsetInMicroseconds - tsk.EventType = "B" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - - tsk.Name = "" - tsk.Ts = int64(e.Timestamp/1000) - timeOffsetInMicroseconds - tsk.EventType = "E" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte(",")) - - startTime = (e.Timestamp / 1000) - } +func loadTextures(after *path.Command) action { + return ¶llel{ + label: "textures", + actions: []action{ + loadTextureDatas(after), + loadTextureThumbnails(after), + }, + } +} + +func loadShader(after *path.Command, shader *service.Resource) action { + return &simple{ + shader.Handle, + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, after.ResourceAfter(shader.ID).Path(), b.resolveConfig()) + }, + } +} - tsk.Name = "" - tsk.Ts = int64(startTime) - timeOffsetInMicroseconds - tsk.EventType = "E" - b, _ = json.Marshal(tsk) - f.Write([]byte("\n")) - f.Write(b) - f.Write([]byte("]")) +func loadShaders(after *path.Command) action { + return &simple{ + "shaders", + func(ctx context.Context, b *benchmark) (interface{}, error) { + shaders := b.shaders(after) + actions := make([]action, len(shaders)) - writeOutput() - return nil + for i := range shaders { + actions[i] = loadShader(after, shaders[i]) + } + + return b.measure(ctx, ¶llel{ + label: "load", + actions: actions, + }) + }, } +} + +func loadStateTree(after *path.Command) action { + return &simple{ + "state_tree", + func(ctx context.Context, b *benchmark) (interface{}, error) { + treeBoxed, err := b.client.Get(ctx, after.StateTreeAfter(2000).Path(), b.resolveConfig()) + if err != nil { + return nil, err + } + tree := &stateTree{ + path: treeBoxed.(*service.StateTree).GetRoot(), + } + if _, err := b.measure(ctx, loadStateTreeNode(tree)); err != nil { + return nil, err + } - if verb.DumpTrace == "" { - writeOutput() + if _, err := b.measure(ctx, expandStateTreeNode(tree)); err != nil { + return nil, err + } + + return tree, nil + }, } - return nil } -// This intentionally duplicates a lot of the gapit trace logic -// so that we can independently chnage how what we do to benchmark -// everything. -func (verb *benchmarkVerb) doTrace(ctx context.Context, client client.Client, traceURI string) error { - ctx = status.Start(ctx, "Record Trace for %+v frames", verb.NumFrames) - defer status.Finish(ctx) +func loadStateTreeNode(tree *stateTree) action { + return &simple{ + "state_tree_node<" + printIndices(tree.path.GetIndices()) + ">", + func(ctx context.Context, b *benchmark) (interface{}, error) { + node, err := b.client.Get(ctx, tree.path.Path(), b.resolveConfig()) + if err != nil { + return nil, err + } + tree.node = node.(*service.StateTreeNode) + return node, nil + }, + } +} - // Find the actual trace URI from all of the devices - _, err := client.GetServerInfo(ctx) - if err != nil { - return err +func expandStateTreeNode(tree *stateTree) action { + actions := make([]action, tree.node.GetNumChildren()) + tree.children = make([]stateTree, tree.node.GetNumChildren()) + for i := range actions { + tree.children[i].path = tree.path.Index(uint64(i)) + actions[i] = loadStateTreeNode(&tree.children[i]) + } + return ¶llel{ + "state_tree_expand<" + printIndices(tree.path.GetIndices()) + ">", + actions, + } +} + +func loadGeometryMetadata(tree *cmdTree) action { + return &simple{ + "mesh_metadata", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, tree.path.Mesh(&path.MeshOptions{ExcludeData: true}).Path(), b.resolveConfig()) + }, } +} - devices, err := client.GetDevices(ctx) - if err != nil { - return err +func loadGeometryMesh(tree *cmdTree, faceted bool) action { + name := "mesh" + if faceted { + name = "mesh_faceted" + } + + return &simple{ + name, + func(ctx context.Context, b *benchmark) (interface{}, error) { + return ignoreDataUnavailable( + b.client.Get(ctx, tree.path.Mesh(path.NewMeshOptions(faceted)).As(meshFormat).Path(), b.resolveConfig())) + }, + } +} + +func loadGeometry(tree *cmdTree) action { + return &sequential{ + label: "geometry", + actions: []action{ + loadGeometryMetadata(tree), + ¶llel{ + label: "meshes", + actions: []action{ + loadGeometryMesh(tree, false), + loadGeometryMesh(tree, true), + }, + }, + }, + } +} + +func loadPipeline(tree *cmdTree) action { + return &simple{ + "pipeline", + func(ctx context.Context, b *benchmark) (interface{}, error) { + return b.client.Get(ctx, tree.path.Pipelines().Path(), b.resolveConfig()) + }, + } +} + +func selectCommand(tree *cmdTree, isDraw bool) action { + after := tree.node.GetCommands().Last() + actions := []action{ + loadFrameBuffer(after), + loadTextures(after), + loadStateTree(after), + } + if isDraw { + actions = append(actions, + loadGeometry(tree), + loadPipeline(tree), + ) + } + return ¶llel{ + label: "select_command<" + printIndices(after.GetIndices()) + ">", + actions: actions, + } +} + +func selectCommandByAction(selCommand action, isDraw bool) action { + return &simple{ + "find_and_select_command", + func(ctx context.Context, b *benchmark) (interface{}, error) { + node, err := b.measure(ctx, selCommand) + if err != nil { + return nil, err + } + + return b.measure(ctx, selectCommand(node.(*cmdTree), isDraw)) + }, } +} - devices, err = filterDevices(ctx, &verb.DeviceFlags, client) +func writeOutput(ctx context.Context, path string, f func(w io.Writer) error) error { + out, err := os.Create(path) if err != nil { - return err + return log.Errf(ctx, err, "Failed to create output file: %s", path) } - if len(devices) == 0 { - return fmt.Errorf("Could not find matching device") + defer out.Close() + + if err := f(out); err != nil { + return log.Errf(ctx, err, "Failed to write output file: %s", path) } + return nil +} - type info struct { - uri string - device *path.Device - deviceName string - name string +func (verb *benchmarkVerb) Run(ctx context.Context, flags flag.FlagSet) error { + if flags.NArg() != 1 { + app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg()) + return nil } - var found []info - for _, dev := range devices { - targets, err := client.FindTraceTargets(ctx, &service.FindTraceTargetsRequest{ - Device: dev, - Uri: traceURI, - }) - if err != nil { - continue - } + b := &benchmark{ + rnd: newRandom(flags.Arg(0), verb.Seed), + } - dd, err := client.Get(ctx, dev.Path(), nil) - if err != nil { - return err - } - d := dd.(*device.Instance) - - for _, target := range targets { - name := target.Name - switch { - case target.FriendlyApplication != "": - name = target.FriendlyApplication - case target.FriendlyExecutable != "": - name = target.FriendlyExecutable - } - - found = append(found, info{ - uri: target.Uri, - deviceName: d.Name, - device: dev, - name: name, - }) - } + if verb.DumpTrace != "" { + b.stopGapitTrace = status.RegisterTracer(&b.gapitTrace) + + defer func() { + b.stopGapitTrace() + + f, err := os.Create(verb.DumpTrace) + if err != nil { + log.E(ctx, "Failed to create trace file: %v", err) + return + } + defer f.Close() + + _, err = f.Write(b.gapitTrace.Bytes()) + if err != nil { + log.E(ctx, "Failed to write gapit trace data: %v", err) + return + } + if b.gapisTrace.Len() > 1 { + // Skip the leading [ + _, err = f.Write(b.gapisTrace.Bytes()[1:]) + if err != nil { + log.E(ctx, "Failed to write gapis trace data: %v", err) + return + } + } + }() } - if len(found) == 0 { - return fmt.Errorf("Could not find %+v to trace on any device", traceURI) + actions := []action{ + &sequential{ + label: "init", + actions: []action{ + &startGapis{&verb.Gapis, &verb.Gapir, verb.DumpTrace != ""}, + loadCapture(flags.Arg(0)), + loadReplaydevice(), + ¶llel{ + label: "load", + actions: []action{ + loadResources(), + loadCommandTree(), + loadProfilingData(), + }, + }, + }, + }, } - if len(found) > 1 { - sb := strings.Builder{} - fmt.Fprintf(&sb, "Found %v candidates: \n", traceURI) - for i, f := range found { - if i == 0 || found[i-1].deviceName != f.deviceName { - fmt.Fprintf(&sb, " %v:\n", f.deviceName) + if len(verb.Paths) == 0 { + draws := verb.NumDraws + if draws == 0 { + draws = defaultNumDraws + } + for i := 0; i < draws; i++ { + actions = append(actions, selectCommandByAction(findDrawCall(nil, verb.Secondary), true)) + } + } else { + maxPathElements := 5 + if verb.Secondary { + maxPathElements = 7 + } + for _, path := range verb.Paths { + draws := verb.NumDraws + if len(path) > maxPathElements { + return fmt.Errorf("Invalid path: %v - too long", path) + } else if len(path) == maxPathElements { + draws = 1 + } else if draws == 0 { + draws = defaultNumDraws + } + + for i := 0; i < draws; i++ { + actions = append(actions, selectCommandByAction(findDrawCall(path, verb.Secondary), true)) } - fmt.Fprintf(&sb, " %v\n", f.uri) } - return log.Errf(ctx, nil, "%v", sb.String()) - } - - out := BenchmarkName - uri := found[0].uri - traceDevice := found[0].device - - options := &service.TraceOptions{ - Device: traceDevice, - Apis: []string{}, - AdditionalCommandLineArgs: verb.AdditionalArgs, - Cwd: verb.WorkingDir, - Environment: verb.Env, - Duration: 0, - ObserveFrameFrequency: 0, - ObserveDrawFrequency: 0, - StartFrame: uint32(verb.StartFrame), - FramesToCapture: uint32(verb.NumFrames), - DisablePcs: true, - RecordErrorState: false, - DeferStart: false, - NoBuffer: false, - HideUnknownExtensions: true, - RecordTraceTimes: true, - ClearCache: false, - ServerLocalSavePath: out, - } - options.App = &service.TraceOptions_Uri{ - uri, - } - switch verb.API { - case "vulkan": - options.Apis = []string{"Vulkan"} - case "gles": - options.Apis = []string{"OpenGLES"} - case "": - return fmt.Errorf("Please specify one of vulkan or gles for an api") - default: - return fmt.Errorf("Unknown API %s", verb.API) - } - verb.beforeStartTraceTime = time.Now() - handler, err := client.Trace(ctx) - if err != nil { - return err } - defer handler.Dispose(ctx) - defer app.AddInterruptHandler(func() { - handler.Dispose(ctx) - })() + group := &sequential{ + label: "root", + actions: actions, + } - status, err := handler.Initialize(ctx, options) + _, err := group.exec(ctx, b) + group.cleanup(ctx, b, err) if err != nil { return err } - verb.traceInitializedTime = time.Now() - return task.Retry(ctx, 0, time.Second*3, func(ctx context.Context) (retry bool, err error) { - status, err = handler.Event(ctx, service.TraceEvent_Status) - if err == io.EOF { - return true, nil - } - if err != nil { - log.I(ctx, "Error %+v", err) - return true, err + if verb.CsvOut != "" { + if err := writeOutput(ctx, verb.CsvOut, b.measurement.writeCsv); err != nil { + return err } - if status == nil { - return true, nil + } + + if verb.DotOut != "" { + if err := writeOutput(ctx, verb.DotOut, b.measurement.writeGraph); err != nil { + return err } + } - if status.Status == service.TraceStatus_Done { - return true, nil + summary := b.measurement.computeSummary() + if verb.SummaryOut != "" { + if err := writeOutput(ctx, verb.SummaryOut, summary.print); err != nil { + return err } - return false, nil - }) + } else { + log.I(ctx, "-----------------------------------") + if err := summary.print(log.From(ctx).Writer(log.Info)); err != nil { + return err + } + log.I(ctx, "-----------------------------------") + } + + return nil } diff --git a/cmd/gapit/coarse_profile.go b/cmd/gapit/coarse_profile.go index f693419b63..ebe56378a4 100644 --- a/cmd/gapit/coarse_profile.go +++ b/cmd/gapit/coarse_profile.go @@ -90,9 +90,8 @@ func (verb *coarseProfileVerb) Run(ctx context.Context, flags flag.FlagSet) erro } req := &service.GetTimestampsRequest{ - Capture: capturePath, - Device: device, - LoopCount: int32(verb.LoopCount), + Capture: capturePath, + Device: device, } client.GetTimestamps(ctx, req, func(r *service.GetTimestampsResponse) error { diff --git a/cmd/gapit/commands.go b/cmd/gapit/commands.go index 872540dca4..efcbf715a9 100644 --- a/cmd/gapit/commands.go +++ b/cmd/gapit/commands.go @@ -33,12 +33,9 @@ type commandsVerb struct{ CommandsFlags } func init() { verb := &commandsVerb{ CommandsFlags: CommandsFlags{ - CommandFilterFlags: CommandFilterFlags{ - Context: -1, - }, + CommandFilterFlags: CommandFilterFlags{}, }, } - verb.Context = -1 app.AddVerb(&app.Verb{ Name: "commands", ShortHelp: "Prints the command tree for a .gfxtrace file", @@ -62,16 +59,14 @@ func (verb *commandsVerb) Run(ctx context.Context, flags flag.FlagSet) error { if err != nil { return log.Err(ctx, err, "Failed to build the CommandFilter") } + filter.OnlyExecutedDraws = verb.OnlyExecutedDraws + filter.OnlyEndOfFrames = verb.OnlyEndOfFrames treePath := capture.CommandTree(filter) - treePath.GroupByApi = verb.GroupByAPI - treePath.GroupByContext = verb.GroupByContext - treePath.GroupByThread = verb.GroupByThread treePath.GroupByDrawCall = verb.GroupByDrawCall treePath.GroupByFrame = verb.GroupByFrame treePath.GroupByUserMarkers = verb.GroupByUserMarkers treePath.GroupBySubmission = verb.GroupBySubmission - treePath.IncludeNoContextGroups = verb.IncludeNoContextGroups treePath.AllowIncompleteFrame = verb.AllowIncompleteFrame treePath.MaxChildren = int32(verb.MaxChildren) @@ -107,10 +102,17 @@ func (verb *commandsVerb) Run(ctx context.Context, flags flag.FlagSet) error { } return traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { - fmt.Fprintf(os.Stdout, prefix) - if n.Group != "" { - fmt.Fprintln(os.Stdout, n.Group) - return nil + if verb.OnlyExecutedDraws { + // Filter out queue submits, which either have children or singular command indices + if n.Group != "" || n.NumChildren > 0 || len(n.Commands.First().Indices) == 1 { + return nil + } + } else { + fmt.Fprintf(os.Stdout, prefix) + if n.Group != "" { + fmt.Fprintln(os.Stdout, n.Group) + return nil + } } return getAndPrintCommand(ctx, client, n.Commands.First(), verb.Observations) }, "", true) diff --git a/cmd/gapit/common.go b/cmd/gapit/common.go index 484abf66a1..0eff126e90 100644 --- a/cmd/gapit/common.go +++ b/cmd/gapit/common.go @@ -47,37 +47,6 @@ import ( func (f CommandFilterFlags) commandFilter(ctx context.Context, client service.Service, p *path.Capture) (*path.CommandFilter, error) { filter := &path.CommandFilter{} - if f.ContextName != "" { - boxedContexts, err := client.Get(ctx, p.Contexts().Path(), nil) - if err != nil { - return nil, log.Err(ctx, err, "Failed to load the contexts") - } - contextList := boxedContexts.(*service.Contexts).List - - for i, c := range contextList { - boxedContext, err := client.Get(ctx, p.Context(c.ID.ID()).Path(), nil) - if err != nil { - return nil, log.Errf(ctx, err, "Failed to load context at index %d", i) - } - context := boxedContext.(*service.Context) - if f.ContextName == context.Name { - filter.Context = c.ID - return filter, nil - } - } - - return nil, log.Errf(ctx, err, "Could not find context named %s", f.ContextName) - } else if f.Context >= 0 { - boxedContexts, err := client.Get(ctx, p.Contexts().Path(), nil) - if err != nil { - return nil, log.Err(ctx, err, "Failed to load the contexts") - } - contexts := boxedContexts.(*service.Contexts) - if n := len(contexts.List); f.Context >= n { - return nil, log.Errf(ctx, err, "Context %d is out of range [0..%d]", f.Context, n-1) - } - filter.Context = contexts.List[f.Context].ID - } return filter, nil } @@ -106,7 +75,18 @@ func getGapis(ctx context.Context, gapisFlags GapisFlags, gapirFlags GapirFlags) // gapir argument string for gapis. args = append(args, "--gapir-args", gapirFlags.Args) } - args = append(args, "--idle-timeout", "1m") + + // Default idle timeout to 1m + foundIdleTimeout := false + for _, s := range args { + if s == "-idle-timeout" || s == "--idle-timeout" { + foundIdleTimeout = true + break + } + } + if !foundIdleTimeout { + args = append(args, "--idle-timeout", "1m") + } var token auth.Token if gapisFlags.Port == 0 { @@ -235,7 +215,7 @@ func getDevice(ctx context.Context, client client.Client, capture *path.Capture, return nil, nil } ctx = log.V{"device": flags.Device}.Bind(ctx) - paths, err := client.GetDevicesForReplay(ctx, capture) + paths, compatibilities, _, err := client.GetDevicesForReplay(ctx, capture) if err != nil { return nil, log.Err(ctx, err, "Failed query list of devices for replay") } @@ -249,6 +229,10 @@ func getDevice(ctx context.Context, client client.Client, capture *path.Capture, return d.GetName() } for i := 0; i < len(paths); i++ { + if !compatibilities[i] { + continue + } + p := paths[i] o, err := client.Get(ctx, p.Path(), nil) if err != nil { @@ -375,14 +359,6 @@ func getADBDevice(ctx context.Context, pattern string) (adb.Device, error) { return matchingDevices[0], nil } -func getEvents(ctx context.Context, client service.Service, p *path.Events) ([]*service.Event, error) { - b, err := client.Get(ctx, p.Path(), nil) - if err != nil { - return nil, log.Errf(ctx, err, "Couldn't get events at: %v", p) - } - return b.(*service.Events).List, nil -} - func getCommand(ctx context.Context, client service.Service, p *path.Command) (*api.Command, error) { boxedCmd, err := client.Get(ctx, p.Path(), nil) if err != nil { diff --git a/cmd/gapit/dump_fbo.go b/cmd/gapit/dump_fbo.go index b1ec423a0c..749f690f34 100644 --- a/cmd/gapit/dump_fbo.go +++ b/cmd/gapit/dump_fbo.go @@ -78,42 +78,37 @@ func (verb *dumpFBOVerb) frameSource(ctx context.Context, client client.Client, if err != nil { return nil, log.Err(ctx, err, "Couldn't get filter") } + filter.OnlyFramebufferObservations = true - return func(ch chan<- image.Image) error { - // Get the draw call and end-of-frame events. - events, err := getEvents(ctx, client, &path.Events{ - Capture: capture, - LastInFrame: true, - FramebufferObservations: true, - Filter: filter, - }) - if err != nil { - return log.Err(ctx, err, "Couldn't get events") + treePath := capture.CommandTree(filter) + + boxedTree, err := client.Get(ctx, treePath.Path(), nil) + if err != nil { + return nil, log.Err(ctx, err, "Failed to load the command tree") + } + + tree := boxedTree.(*service.CommandTree) + + var allFBOCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil } + allFBOCommands = append(allFBOCommands, n.Commands.First()) + return nil + }, "", true) - var lastFrameEvent *path.Command - for _, e := range events { - switch e.Kind { - case service.EventKind_FramebufferObservation: - // Find FBO for all presents. - if lastFrameEvent == nil { - continue - } - lastFrameEvent = nil - - fbo, err := getFBO(ctx, client, e.Command) - if err != nil { - return err - } - - ch <- flipImg(&image.NRGBA{ - Pix: fbo.Bytes, - Stride: int(fbo.Width) * 4, - Rect: image.Rect(0, 0, int(fbo.Width), int(fbo.Height)), - }) - case service.EventKind_LastInFrame: - lastFrameEvent = e.Command + return func(ch chan<- image.Image) error { + for _, cmd := range allFBOCommands { + fbo, err := getFBO(ctx, client, cmd) + if err != nil { + return err } + ch <- flipImg(&image.NRGBA{ + Pix: fbo.Bytes, + Stride: int(fbo.Width) * 4, + Rect: image.Rect(0, 0, int(fbo.Width), int(fbo.Height)), + }) } return nil diff --git a/cmd/gapit/dump_pipeline.go b/cmd/gapit/dump_pipeline.go index a3fa95e6e4..3083ad6cee 100644 --- a/cmd/gapit/dump_pipeline.go +++ b/cmd/gapit/dump_pipeline.go @@ -86,7 +86,7 @@ func (verb *pipeVerb) getBoundPipelineResource(ctx context.Context, c client.Cli resources := boxedResources.(*service.Resources) for _, typ := range resources.Types { - if typ.Type != api.ResourceType_PipelineResource { + if typ.Type != path.ResourceType_Pipeline { continue } @@ -96,7 +96,7 @@ func (verb *pipeVerb) getBoundPipelineResource(ctx context.Context, c client.Cli return nil, log.Err(ctx, err, "Failed to load the pipeline resource") } resourceData := boxedResourceData.(*api.ResourceData) - pipelineData := protoutil.OneOf(protoutil.OneOf(resourceData)).(*api.Pipeline) + pipelineData := protoutil.OneOf(resourceData.Data).(*api.Pipeline) if pipelineData.Bound && pipelineData.PipelineType == targetType { return pipelineData, nil } @@ -126,6 +126,9 @@ func toString(dataval *api.DataValue) string { case *api.DataValue_Bitfield: return strings.Join(x.Bitfield.SetBitnames, " | ") + + case *api.DataValue_Link: + return toString(x.Link.DisplayVal) } return "" diff --git a/cmd/gapit/dump_shaders.go b/cmd/gapit/dump_shaders.go index 6625264b03..9d3a6ad650 100644 --- a/cmd/gapit/dump_shaders.go +++ b/cmd/gapit/dump_shaders.go @@ -76,7 +76,7 @@ func (verb *dumpShadersVerb) Run(ctx context.Context, flags flag.FlagSet) error } for _, types := range resources.GetTypes() { - if types.Type == api.ResourceType_ShaderResource { + if types.Type == path.ResourceType_Shader { for _, v := range types.GetResources() { if !v.ID.IsValid() { log.E(ctx, "Got resource with invalid ID!\n%+v", v) diff --git a/cmd/gapit/export_replay.go b/cmd/gapit/export_replay.go index 8724344b0f..5f430c5f19 100644 --- a/cmd/gapit/export_replay.go +++ b/cmd/gapit/export_replay.go @@ -34,7 +34,6 @@ import ( "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/file" "github.com/google/gapid/core/os/shell" - "github.com/google/gapid/gapis/api" "github.com/google/gapid/gapis/service" gapidPath "github.com/google/gapid/gapis/service/path" ) @@ -101,7 +100,7 @@ func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error } } - var fbreqs []*service.GetFramebufferAttachmentRequest + var fbreqs []*gapidPath.FramebufferAttachment var tsreq *service.GetTimestampsRequest onscreen := false switch verb.Mode { @@ -114,41 +113,43 @@ func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error if err != nil { return log.Err(ctx, err, "Couldn't get filter") } + filter.OnlyEndOfFrames = true - requestEvents := gapidPath.Events{ - Capture: capturePath, - LastInFrame: true, - Filter: filter, - } + treePath := capturePath.CommandTree(filter) - // Get the end-of-frame events. - eofEvents, err := getEvents(ctx, client, &requestEvents) + boxedTree, err := client.Get(ctx, treePath.Path(), nil) if err != nil { - return log.Err(ctx, err, "Couldn't get frame events") + return log.Err(ctx, err, "Failed to load the command tree") } - for _, e := range eofEvents { - fbreqs = append(fbreqs, &service.GetFramebufferAttachmentRequest{ - ReplaySettings: &service.ReplaySettings{ - Device: device, - DisableReplayOptimization: true, - }, - After: e.Command, - Attachment: api.FramebufferAttachment_Color0, - Settings: &service.RenderSettings{}, - Hints: nil, + tree := boxedTree.(*service.CommandTree) + + var allEOFCommands []*gapidPath.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil + } + allEOFCommands = append(allEOFCommands, n.Commands.First()) + return nil + }, "", true) + + for _, e := range allEOFCommands { + fbreqs = append(fbreqs, &gapidPath.FramebufferAttachment{ + After: e, + Index: 0, + RenderSettings: &gapidPath.RenderSettings{DisableReplayOptimization: true}, + Hints: nil, }) } case ExportTimestamps: // There are no useful field in GetTimestampsRequest as of now. - tsreq = &service.GetTimestampsRequest{LoopCount: int32(verb.LoopCount)} + tsreq = &service.GetTimestampsRequest{} } opts := &service.ExportReplayOptions{ - GetFramebufferAttachmentRequests: fbreqs, - GetTimestampsRequest: tsreq, - DisplayToSurface: onscreen, - LoopCount: int32(verb.LoopCount), + FramebufferAttachments: fbreqs, + GetTimestampsRequest: tsreq, + DisplayToSurface: onscreen, } if err := client.ExportReplay(ctx, capturePath, device, verb.Out, opts); err != nil { @@ -225,10 +226,10 @@ func (verb *exportReplayVerb) Run(ctx context.Context, flags flag.FlagSet) error // find latest build tools sdkPath := verb.SdkPath if sdkPath == "" { - sdkPath = os.ExpandEnv("${ANDROID_SDK_HOME}") + sdkPath = os.ExpandEnv("${ANDROID_SDK_ROOT}") } if _, err := os.Stat(sdkPath); err != nil { - return log.Err(ctx, err, "Cannot find Android SDK. Please set ANDROID_SDK_HOME, or use the -sdkpath flag") + return log.Err(ctx, err, "Cannot find Android SDK. Please set ANDROID_SDK_ROOT, or use the -sdkpath flag") } toolsPathParent := path.Join(sdkPath, "build-tools") matches, err := filepath.Glob(path.Join(toolsPathParent, "*")) diff --git a/cmd/gapit/flags.go b/cmd/gapit/flags.go index 63dec010cd..2783b566af 100644 --- a/cmd/gapit/flags.go +++ b/cmd/gapit/flags.go @@ -26,6 +26,7 @@ const ( SxsVideo RegularVideo IndividualFrames + SxsFrames ) const ( @@ -62,6 +63,7 @@ var videoTypeNames = map[VideoType]string{ SxsVideo: "sxs", RegularVideo: "regular", IndividualFrames: "frames", + SxsFrames: "sxs-frames", } func (v *VideoType) Choose(c interface{}) { @@ -139,8 +141,6 @@ type ( CaptureID bool `help:"if true then interpret the capture file argument as a capture ID that is already loaded in gapis"` } CommandFilterFlags struct { - Context int `help:"Filter to the i'th context. Does nothing with -contextname."` - ContextName string `help:"Filter by context name."` } ObservationFlags struct { Ranges bool `help:"if true then display the read and write ranges made by each command."` @@ -184,7 +184,6 @@ type ( Gapir GapirFlags Out string `help:"output report path"` DisplayToSurface bool `help:"display the frames rendered in the replay back to the surface"` - CommandFilterFlags CaptureFileFlags } ExportReplayFlags struct { @@ -194,7 +193,7 @@ type ( Out string `help:"output directory for commands and assets"` Mode ExportMode `help:"generate special purposed trace"` Apk string `help:"(experimental) name of the stand-alone APK created to perform the replay. This name must be .apk (e.g. com.example.replay.apk)"` - SdkPath string `help:"Path to Android SDK directory (default: ANDROID_SDK_HOME environment variable)"` + SdkPath string `help:"Path to Android SDK directory (default: ANDROID_SDK_ROOT environment variable)"` LoopCount int `help:"_The number of times to loop the trace. (experimental)"` CommandFilterFlags CaptureFileFlags @@ -223,7 +222,7 @@ type ( DumpShadersFlags struct { Gapis GapisFlags Gapir GapirFlags - At int `help:"command index to dump the resources after"` + At int `help:"command index to dump the resources after, e.g. '1234'"` CaptureFileFlags } DumpFBOFlags struct { @@ -243,21 +242,19 @@ type ( CaptureFileFlags } CommandsFlags struct { - Gapis GapisFlags - Gapir GapirFlags - Raw bool `help:"if true then the value of constants, instead of their names, will be dumped."` - Name string `help:"Filter to commands and groups with the specified name."` - MaxChildren int `help:"_Maximum children per tree node."` - GroupByAPI bool `help:"Group commands by api"` - GroupByContext bool `help:"Group commands by context"` - GroupByThread bool `help:"Group commands by thread"` - GroupByDrawCall bool `help:"Group commands by draw call"` - GroupByFrame bool `help:"Group commands by frame"` - GroupByUserMarkers bool `help:"Group commands by user markers"` - GroupBySubmission bool `help:"Group commands by submissions"` - IncludeNoContextGroups bool `help:"_Include no context groups"` - AllowIncompleteFrame bool `help:"_Make a group for incomplete frames"` - Observations ObservationFlags + Gapis GapisFlags + Gapir GapirFlags + Raw bool `help:"if true then the value of constants, instead of their names, will be dumped."` + Name string `help:"Filter to commands and groups with the specified name."` + MaxChildren int `help:"_Maximum children per tree node."` + GroupByDrawCall bool `help:"Group commands by draw call"` + GroupByFrame bool `help:"Group commands by frame"` + GroupByUserMarkers bool `help:"Group commands by user markers"` + GroupBySubmission bool `help:"Group commands by submissions"` + AllowIncompleteFrame bool `help:"_Make a group for incomplete frames"` + OnlyExecutedDraws bool `help:"Only show executed draw calls from within command buffers"` + OnlyEndOfFrames bool `help:"Only end of frame commands"` + Observations ObservationFlags CommandFilterFlags CaptureFileFlags } @@ -266,16 +263,24 @@ type ( Gapir GapirFlags Handle string `help:"required. handle or ID of the resource to replace"` ResourcePath string `help:"file path for the new resource"` - At int `help:"command index to replace the resource(s) at"` + At int `help:"command index to replace the resource(s) at, e.g. '1234'"` UpdateResourceBinary string `help:"shaders only. binary to run for every shader; consumes resource data from standard input and writes to standard output"` OutputTraceFile string `help:"file name for the updated trace"` SkipOutput bool `help:"skip writing the modified trace to a file"` CaptureFileFlags } + ServerPerformanceFlags struct { + Gapis GapisFlags + Gapir GapirFlags + OriginalDevice bool `help:"export replay for the original device"` + LoopCount int `help:"_The number of times to loop the trace. (experimental)"` + CommandFilterFlags + CaptureFileFlags + } StateFlags struct { Gapis GapisFlags Gapir GapirFlags - At flags.U64Slice `help:"command/subcommand index to get the state after. 0 for first command. Empty for last"` + At flags.U64Slice `help:"command/subcommand index to get the state after, e.g. '[123, 0, 0, 4]'. 0 for first command. Empty for last"` Depth int `help:"How many nodes deep should the state tree be displayed. -1 for all"` Filter flags.StringSlice `help:"Which path (e.g. '[root, Devices]') through the tree should we filter to, default All"` CaptureFileFlags @@ -295,17 +300,14 @@ type ( URI string `help:"uri of the application to trace"` Observe struct { Frames uint `help:"capture the framebuffer every n frames (0 to disable)"` - Draws uint `help:"capture the framebuffer every n draws (0 to disable)"` } Disable struct { - PCS bool `help:"disable pre-compiled shaders"` Unknown struct { Extensions bool `help:"Hide unknown extensions from the application."` } CoherentMemoryTracker bool `help:"_disables the coherent memory tracker so it won't interfere with gdb during trace"` } Record struct { - Errors bool `help:"_record device error state"` TraceTimes bool `help:"record trace timing into the capture"` } Clear struct { @@ -320,29 +322,31 @@ type ( Capture struct { Frames int `help:"only capture the given number of frames. 0 for all"` } - No struct { + UseAndroidFrameBoundary bool `help:"When enabled, AGI uses ANDROID_frame_boundary extension as frame delimiters. Otherwise, present calls are used. Default is false."` + No struct { Buffer bool `help:"Do not buffer the output, this helps if the application crashes"` } - API string `help:"only capture the given API valid options are gles, vulkan, and perfetto"` + API string `help:"only capture the given API valid options are vulkan, angle and perfetto"` Local struct { Port int `help:"connect to an application already running on the server using this port"` } - PipeName string `help:"The name of the pipe to connect/listen to."` - Perfetto string `help:"File containing the Perfetto configuration proto."` + PipeName string `help:"The name of the pipe to connect/listen to."` + Perfetto string `help:"File containing the Perfetto configuration proto."` + WaitForDebugger bool `help:"Make GAPII wait for a debugger to attach"` + ProcessName string `help:"Name of the process to capture. Default to empty, i.e. capture any process. Useful for games that fork processes."` + LoadValidationLayer bool `help:"Load Vulkan validation layer at capture time, under the spy layer, to debug spy bugs. Android only."` } BenchmarkFlags struct { - DeviceFlags - Gapis GapisFlags - Gapir GapirFlags - NumFrames int `help:"how many frames to capture"` - AdditionalArgs string `help:"additional arguments to pass to the application"` - WorkingDir string `help:"working directory for the application"` - URI string `help:"uri of the application to trace"` - API string `help:"only capture the given API valid options are gles and vulkan"` - DumpTrace string `help:"dump a systrace of gapis"` - StartFrame int `help:"perform a MEC trace starting at this frame"` - NoOpt bool `help:"disables optimization of the replay stream"` - OutputCSV bool `help:"outputs data in CSV-friendly format"` + Gapis GapisFlags + Gapir GapirFlags + NumDraws int `help:"the number of draw calls to select"` + Paths []flags.U64Slice `help:"the (partial) paths to the draw calls to use"` + Secondary bool `help:"whether to expect secondary command buffers inside the renderpasses"` + Seed int64 `help:"seed to use when making RNG decisions"` + DumpTrace string `help:"dump a combined systrace of gapit and gapis"` + SummaryOut string `help:"filename to write summary to, default is stdout"` + CsvOut string `help:"dump a csv file with the detailed stats"` + DotOut string `help:"dump a dot file with the detailed stats"` } StatusFlags struct { @@ -362,19 +366,21 @@ type ( ADB string `help:"Path to the adb executable; leave empty to search the environment"` } ScreenshotFlags struct { - Gapis GapisFlags - Gapir GapirFlags - At []flags.U64Slice `help:"command/subcommand index for the screenshot (repeatable)"` - Frame []int `help:"frame index for the screenshot (repeatable). Empty for last"` - Draws bool `help:"create a screenshot of every draw call in the requested frame(s) (only honored if using -frame)"` - Out string `help:"output image file (default 'screenshot.png')"` - NoOpt bool `help:"disables optimization of the replay stream"` - Attachment string `help:"the attachment to show (0-3 for color, d for depth, s for stencil)"` - Overdraw bool `help:"renders the overdraw instead of the color framebuffer"` - Max struct { + Gapis GapisFlags + Gapir GapirFlags + At []flags.U64Slice `help:"command/subcommand index (e.g. '[123, 0, 0, 4]') for the screenshot (repeatable)"` + Frame []int `help:"frame index for the screenshot (repeatable). Empty for last"` + Draws bool `help:"create a screenshot of every draw call in the requested frame(s) (only honored if using -frame)"` + ExecutedDraws int `help:"create a screenshot at every (total-draw-calls / this-flag-value) drawcall, e.g. for 100 draw calls, '-executeddraws 4' takes a screenshot at every 25th call"` + Out string `help:"output image file (default 'screenshot.png')"` + NoOpt bool `help:"disables optimization of the replay stream"` + Attachment string `help:"the attachment to show (0-3 for color, d for depth, s for stencil)"` + Overdraw bool `help:"renders the overdraw instead of the color framebuffer"` + Max struct { Overdraw int `help:"the amount of overdraw to map to white in the output"` } - DisplayToSurface bool `help:"display the frames rendered in the replay back to the surface"` + Size string `help:"requested framebuffer size (scales output). Either a single integer used as width and height, or x"` + DisplayToSurface bool `help:"display the frames rendered in the replay back to the surface"` CommandFilterFlags CaptureFileFlags } @@ -384,12 +390,12 @@ type ( MemoryFlags struct { Gapis GapisFlags - At flags.U64Slice `help:"command/subcommand index to get the memory after. Empty for last"` + At flags.U64Slice `help:"command/subcommand index to get the memory after, e.g. '[123, 0, 0, 4]'. Empty for last"` CaptureFileFlags } PipelineFlags struct { Gapis GapisFlags - At flags.U64Slice `help:"command/subcommand index to get the pipeline after. Empty for last"` + At flags.U64Slice `help:"command/subcommand index to get the pipeline after, e.g. '[123, 0, 0, 4]'. Empty for last"` Print struct { Shaders bool `help:"print the disassembled shaders along with the bound descriptor values"` } @@ -409,6 +415,12 @@ type ( CommandFilterFlags CaptureFileFlags } + TrimStateFlags struct { + Gapis GapisFlags + Gapir GapirFlags + Out string `help:"gfxtrace file to save the trimmed capture"` + CaptureFileFlags + } GetTimestampsFlags struct { Gapis GapisFlags Gapir GapirFlags @@ -417,9 +429,12 @@ type ( } GpuProfileFlags struct { - Gapis GapisFlags - Gapir GapirFlags - Json bool `help:"Return replay profiling data as JSON instead of text"` + Gapis GapisFlags + Gapir GapirFlags + Out string `help:"Output file (optional, if none then output goes to stdout)"` + Json bool `help:"Return replay profiling data as JSON instead of text"` + DisabledCmds []flags.U64Slice `help:"command/subcommand index (e.g. '[123, 0, 0, 4]') for disabling a draw call (repeatable)"` + DisableAF bool `help:"Disable Anisotropic Filtering for all samplers"` } CreateGraphVisualizationFlags struct { @@ -428,6 +443,12 @@ type ( Format string `help:"output format of the graph: 'pbtxt' (Tensorboard) or 'dot' (Graphviz)"` } + FramegraphFlags struct { + Gapis GapisFlags + Dot string `help:"Store the framegraph in Graphviz dot format in this file"` + Json string `help:"Store the framegraph in JSON format in this file"` + } + SmokeTestsFlags struct { } @@ -447,10 +468,20 @@ type ( } PerfettoFlags struct { + Gapis GapisFlags Mode PerfettoMode `help:"Run mode: {metrics|interactive}. Default: metrics."` In string `help:"Input file. Refer to documentation for file format."` Categories string `help:"Comma separated list of metric categories from the input file. Only valid if 'metrics' mode is selected."` Out string `help:"Output file."` Format PerfettoOutputFormat `help:"Output file format: {text|json}."` } + + SplitFlags struct { + Gapis GapisFlags + Gapir GapirFlags + CaptureFileFlags + From uint64 `help:"The inclusive start index of the command range. Default: 0 (first command)"` + To uint64 `help:"The exclusive end index of the command range. Default: 0 (last command)"` + Out string `help:"Output file."` + } ) diff --git a/cmd/gapit/framegraph.go b/cmd/gapit/framegraph.go new file mode 100644 index 0000000000..57711717cb --- /dev/null +++ b/cmd/gapit/framegraph.go @@ -0,0 +1,309 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +package main + +import ( + "context" + "flag" + "fmt" + "os" + "strings" + + "github.com/golang/protobuf/jsonpb" + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" + "github.com/google/gapid/gapis/api" +) + +type framegraphVerb struct{ FramegraphFlags } + +func init() { + verb := &framegraphVerb{} + app.AddVerb(&app.Verb{ + Name: "framegraph", + ShortHelp: "Get the frame graph of a capture", + Action: verb, + }) +} + +// Run is the main logic for the 'gapit framegraph' command. +func (verb *framegraphVerb) Run(ctx context.Context, flags flag.FlagSet) error { + if flags.NArg() != 1 { + app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg()) + return nil + } + + if verb.Dot == "" && verb.Json == "" { + app.Usage(ctx, "At least one of -dot or -json flag is expected") + return nil + } + + captureFilename := flags.Arg(0) + + client, capture, err := getGapisAndLoadCapture(ctx, verb.Gapis, GapirFlags{}, captureFilename, CaptureFileFlags{}) + if err != nil { + return err + } + defer client.Close() + + boxedFramegraph, err := client.Get(ctx, capture.Framegraph().Path(), nil) + if err != nil { + return err + } + framegraph := boxedFramegraph.(*api.Framegraph) + + if verb.Json != "" { + err = exportJSON(ctx, framegraph, verb.Json) + } + if err != nil { + return err + } + + if verb.Dot != "" { + err = exportDot(ctx, framegraph, captureFilename, verb.Dot) + } + + return err +} + +func exportJSON(ctx context.Context, framegraph *api.Framegraph, outFile string) error { + file, err := os.Create(outFile) + if err != nil { + return log.Errf(ctx, err, "Creating file (%v)", outFile) + } + defer file.Close() + + m := &jsonpb.Marshaler{ + EmitDefaults: true, + Indent: " ", + } + return m.Marshal(file, framegraph) +} + +// exportDot exports the framegraph in the Graphviz DOT format. +// https://graphviz.org/doc/info/lang.html +// In node labels we use "\l" as a newline to obtain left-aligned text. +func exportDot(ctx context.Context, framegraph *api.Framegraph, captureFilename string, outFile string) error { + file, err := os.Create(outFile) + if err != nil { + return log.Errf(ctx, err, "Creating file (%v)", outFile) + } + defer file.Close() + + fmt.Fprintf(file, "digraph agiFramegraph {\n") + + // Graph title: use capture filename, on top + fmt.Fprintf(file, "label = \"%s\";\n", captureFilename) + fmt.Fprintf(file, "labelloc = \"t\";\n") + // Node style + fmt.Fprintf(file, "node [fontname=Monospace shape=rectangle];\n") + fmt.Fprintf(file, "\n") + // Node IDs cannot start with a digit, so use "n", e.g. n0 n1 n2 + for _, node := range framegraph.Nodes { + fmt.Fprintf(file, fmt.Sprintf("n%v [label=\"%s\"];\n", node.Id, node2dot(node))) + } + fmt.Fprintf(file, "\n") + for _, edge := range framegraph.Edges { + fmt.Fprintf(file, fmt.Sprintf("n%v -> n%v;\n", edge.Origin, edge.Destination)) + } + fmt.Fprintf(file, "}\n") + return nil +} + +func memInfo2dot(coherent bool, mapped bool) string { + memInfo := "" + if coherent || mapped { + memInfo = " memory:" + } + if coherent { + memInfo += " coherent" + } + if mapped { + memInfo += " mapped" + } + return memInfo +} + +func image2dot(img *api.FramegraphImage) string { + usage := "" + + if img.TransferSrc { + usage += " TransferSrc" + } + if img.TransferDst { + usage += " TransferDst" + } + if img.Sampled { + usage += " Sampled" + } + if img.Storage { + usage += " Storage" + } + if img.ColorAttachment { + usage += " ColorAttachment" + } + if img.DepthStencilAttachment { + usage += " DepthStencilAttachment" + } + if img.TransientAttachment { + usage += " TransientAttachment" + } + if img.InputAttachment { + usage += " InputAttachment" + } + if img.Swapchain { + usage += " Swapchain" + } + + imgType := strings.TrimPrefix(fmt.Sprintf("%v", img.ImageType), "VK_IMAGE_TYPE_") + imgFormat := strings.TrimPrefix(fmt.Sprintf("%v", img.Info.Format.Name), "VK_FORMAT_") + memInfo := memInfo2dot(img.CoherentMemory, img.MemoryMapped) + return fmt.Sprintf("[Img:%v %s %s %vx%vx%v usage:%v%s%s]", img.Handle, imgType, imgFormat, img.Info.Width, img.Info.Height, img.Info.Depth, img.Usage, usage, memInfo) +} + +func attachment2dot(att *api.FramegraphAttachment) string { + if att == nil { + return "unused" + } + return fmt.Sprintf("load:%v store:%v %s", att.LoadOp, att.StoreOp, image2dot(att.Image)) +} + +func imageAccess2dot(acc *api.FramegraphImageAccess) string { + r := "-" + if acc.Read { + r = "r" + } + w := "-" + if acc.Write { + w = "w" + } + return fmt.Sprintf("%s%s %s", r, w, image2dot(acc.Image)) +} + +func buffer2dot(buf *api.FramegraphBuffer) string { + usage := "" + + if buf.TransferSrc { + usage += " TransferSrc" + } + if buf.TransferDst { + usage += " TransferDst" + } + if buf.UniformTexel { + usage += " UniformTexel" + } + if buf.StorageTexel { + usage += " StorageTexel" + } + if buf.Uniform { + usage += " Uniform" + } + if buf.Storage { + usage += " Storage" + } + if buf.Index { + usage += " Index" + } + if buf.Vertex { + usage += " Vertex" + } + if buf.Indirect { + usage += " Indirect" + } + + memInfo := memInfo2dot(buf.CoherentMemory, buf.MemoryMapped) + + return fmt.Sprintf("[Buf:%v size:%v usage:%v%s%s]", buf.Handle, buf.Size, buf.Usage, usage, memInfo) +} + +func bufferAccess2dot(acc *api.FramegraphBufferAccess) string { + r := "-" + if acc.Read { + r = "r" + } + w := "-" + if acc.Write { + w = "w" + } + return fmt.Sprintf("%s%s %s", r, w, buffer2dot(acc.Buffer)) +} + +func renderpass2dot(rp *api.FramegraphRenderpass) string { + s := fmt.Sprintf("Renderpass %v\\lbegin:%v\\lend: %v\\lFramebuffer: %vx%vx%v\\l", rp.Handle, rp.BeginSubCmdIdx, rp.EndSubCmdIdx, rp.FramebufferWidth, rp.FramebufferHeight, rp.FramebufferLayers) + for i, subpass := range rp.Subpass { + s += fmt.Sprintf("\\lSubpass %v\\l", i) + for j, a := range subpass.Input { + s += fmt.Sprintf("input(%v): %v\\l", j, attachment2dot(a)) + } + for j, a := range subpass.Color { + s += fmt.Sprintf("color(%v): %v\\l", j, attachment2dot(a)) + } + for j, a := range subpass.Resolve { + s += fmt.Sprintf("resolve(%v): %v\\l", j, attachment2dot(a)) + } + s += fmt.Sprintf("depth/stencil: %v\\l", attachment2dot(subpass.DepthStencil)) + } + + if len(rp.ImageAccess) > 0 { + s += "\\lImage accesses:\\l" + for _, acc := range rp.ImageAccess { + s += fmt.Sprintf("%s\\l", imageAccess2dot(acc)) + } + } + + if len(rp.BufferAccess) > 0 { + s += "\\lBuffer accesses:\\l" + for _, acc := range rp.BufferAccess { + s += fmt.Sprintf("%s\\l", bufferAccess2dot(acc)) + } + } + + return s +} + +func compute2dot(compute *api.FramegraphCompute) string { + s := fmt.Sprintf("Compute\\lcmd:%v\\l", compute.SubCmdIdx) + if compute.Indirect { + s += "Indirect\\l" + } else { + s += fmt.Sprintf("BaseGroupX:%v\\lBaseGroupY:%v\\lBaseGroupZ:%v\\lGroupCountX:%v\\lGroupCountY:%v\\lGroupCountZ:%v\\l", compute.BaseGroupX, compute.BaseGroupY, compute.BaseGroupZ, compute.GroupCountX, compute.GroupCountY, compute.GroupCountZ) + } + + if len(compute.ImageAccess) > 0 { + s += "\\lImage accesses:\\l" + for _, acc := range compute.ImageAccess { + s += fmt.Sprintf("%s\\l", imageAccess2dot(acc)) + } + } + + if len(compute.BufferAccess) > 0 { + s += "\\lBuffer accesses:\\l" + for _, acc := range compute.BufferAccess { + s += fmt.Sprintf("%s\\l", bufferAccess2dot(acc)) + } + } + + return s +} + +func node2dot(node *api.FramegraphNode) string { + if renderpass := node.GetRenderpass(); renderpass != nil { + return renderpass2dot(renderpass) + } + if compute := node.GetCompute(); compute != nil { + return compute2dot(compute) + } + return "INVALID NODE: neither renderpass nor compute" +} diff --git a/cmd/gapit/perfetto.go b/cmd/gapit/perfetto.go index 48fb8d31b3..6ed39e9014 100644 --- a/cmd/gapit/perfetto.go +++ b/cmd/gapit/perfetto.go @@ -104,9 +104,9 @@ func (verb *perfettoVerb) Run(ctx context.Context, flags flag.FlagSet) error { } if verb.Mode == ModeMetrics { - return RunMetrics(ctx, trace, input, categories, output, outputFormat) + return RunMetrics(ctx, verb.Gapis, trace, input, categories, output, outputFormat) } else if verb.Mode == ModeInteractive { - return RunInteractive(ctx, trace, input, output, outputFormat) + return RunInteractive(ctx, verb.Gapis, trace, input, output, outputFormat) } else { return ListMetrics(ctx, input, categories) } @@ -198,7 +198,7 @@ type MetricsInfo struct { MetricCategories []MetricCategory `json:"metrics"` } -func RunMetrics(ctx context.Context, trace string, inputPath string, categories map[string]struct{}, outputPath string, format PerfettoOutputFormat) error { +func RunMetrics(ctx context.Context, gapisFlags GapisFlags, trace string, inputPath string, categories map[string]struct{}, outputPath string, format PerfettoOutputFormat) error { var byteValue []byte if inputPath != "" { metricsFile, err := os.Open(inputPath) @@ -217,7 +217,7 @@ func RunMetrics(ctx context.Context, trace string, inputPath string, categories } // Load the trace - client, capture, err := getGapisAndLoadCapture(ctx, GapisFlags{}, GapirFlags{}, trace, CaptureFileFlags{}) + client, capture, err := getGapisAndLoadCapture(ctx, gapisFlags, GapirFlags{}, trace, CaptureFileFlags{}) if err != nil { return fmt.Errorf("Error while loading the trace file %s: %v.", trace, err) } @@ -285,7 +285,7 @@ func RunMetrics(ctx context.Context, trace string, inputPath string, categories return nil } -func RunInteractive(ctx context.Context, trace string, inputPath string, outputPath string, format PerfettoOutputFormat) error { +func RunInteractive(ctx context.Context, gapisFlags GapisFlags, trace string, inputPath string, outputPath string, format PerfettoOutputFormat) error { // Load the preparation queries from input file. For these queries we do not report the output. var prepQueries []string if inputPath != "" { @@ -313,7 +313,7 @@ func RunInteractive(ctx context.Context, trace string, inputPath string, outputP } // Load the trace - client, capture, err := getGapisAndLoadCapture(ctx, GapisFlags{}, GapirFlags{}, trace, CaptureFileFlags{}) + client, capture, err := getGapisAndLoadCapture(ctx, gapisFlags, GapirFlags{}, trace, CaptureFileFlags{}) if err != nil { return fmt.Errorf("Error while loading the trace file %s: %v.", trace, err) } @@ -729,7 +729,7 @@ func WriteResultsToJSONOutput(metricsResults MetricsResults, outputPath string) jsonResult, err := json.MarshalIndent(&metricsResults, "", " ") if err != nil { - return fmt.Errorf("Error converting the metrics results to Json. Please file a bug and provide as much as information as possbile to address this issue. Error: %v", err) + return fmt.Errorf("Error converting the metrics results to Json. Please file a bug and provide as much as information as possible to address this issue. Error: %v", err) } if _, err := output.Write(jsonResult); err != nil { return fmt.Errorf("Error writing to the output file %s: %v.", outputPath, err) diff --git a/cmd/gapit/profile.go b/cmd/gapit/profile.go index db6b66cf0e..a5f67b91d3 100644 --- a/cmd/gapit/profile.go +++ b/cmd/gapit/profile.go @@ -24,17 +24,22 @@ import ( "github.com/golang/protobuf/proto" "github.com/google/gapid/core/app" + "github.com/google/gapid/core/app/flags" "github.com/google/gapid/core/log" "github.com/google/gapid/gapis/service" + "github.com/google/gapid/gapis/service/path" ) type profileVerb struct{ GpuProfileFlags } func init() { - verb := &profileVerb{GpuProfileFlags{}} + verb := &profileVerb{GpuProfileFlags{ + DisabledCmds: []flags.U64Slice{}, + DisableAF: false, + }} app.AddVerb(&app.Verb{ Name: "profile", - ShortHelp: "Profile a replay to get time slices for gpu render stages.", + ShortHelp: "Profile a replay to get GPU activity and counter data.", Action: verb, }) } @@ -65,9 +70,20 @@ func (verb *profileVerb) Run(ctx context.Context, flags flag.FlagSet) error { return err } + var commands []*path.Command + if len(verb.DisabledCmds) > 0 { + for _, cmd := range verb.DisabledCmds { + commands = append(commands, capturePath.Command(cmd[0], cmd[1:]...)) + } + } + req := &service.GpuProfileRequest{ Capture: capturePath, Device: device, + Experiments: &service.ProfileExperiments{ + DisabledCommands: commands, + DisableAnisotropicFiltering: verb.DisableAF, + }, } res, err := client.GpuProfile(ctx, req) @@ -75,14 +91,23 @@ func (verb *profileVerb) Run(ctx context.Context, flags flag.FlagSet) error { return err } + out := os.Stdout + if verb.Out != "" { + out, err = os.Create(verb.Out) + if err != nil { + return log.Errf(ctx, err, "Creating file (%v)", out) + } + defer out.Close() + } + if verb.Json { jsonBytes, err := json.MarshalIndent(res, "", " ") if err != nil { return log.Err(ctx, err, "Couldn't marshal trace to JSON") } - fmt.Fprintln(os.Stdout, string(jsonBytes)) + fmt.Fprintln(out, string(jsonBytes)) } else { - err = proto.MarshalText(os.Stdout, res) + err = proto.MarshalText(out, res) if err != nil { return log.Err(ctx, err, "Couldn't marshal trace to text") } diff --git a/cmd/gapit/replace_resource.go b/cmd/gapit/replace_resource.go index 0ccbc951ad..2c1f1b99b2 100644 --- a/cmd/gapit/replace_resource.go +++ b/cmd/gapit/replace_resource.go @@ -88,8 +88,8 @@ func (verb *replaceResourceVerb) Run(ctx context.Context, flags flag.FlagSet) er switch { case verb.Handle != "": - matchedResource, err := resources.FindSingle(func(t api.ResourceType, r service.Resource) bool { - return t == api.ResourceType_ShaderResource && + matchedResource, err := resources.FindSingle(func(t path.ResourceType, r service.Resource) bool { + return t == path.ResourceType_Shader && (strings.Contains(r.GetHandle(), verb.Handle) || strings.Contains(r.GetID().ID().String(), verb.Handle)) }) if err != nil { @@ -111,8 +111,8 @@ func (verb *replaceResourceVerb) Run(ctx context.Context, flags flag.FlagSet) er Source: string(newResourceBytes), }) case verb.UpdateResourceBinary != "": - shaderResources := resources.FindAll(func(t api.ResourceType, r service.Resource) bool { - return t == api.ResourceType_ShaderResource + shaderResources := resources.FindAll(func(t path.ResourceType, r service.Resource) bool { + return t == path.ResourceType_Shader }) ids := make([]*path.ID, len(shaderResources)) resourcesSource := make([]*api.ResourceData, len(shaderResources)) diff --git a/cmd/gapit/report.go b/cmd/gapit/report.go index a82e7bb92b..7d89f5695c 100644 --- a/cmd/gapit/report.go +++ b/cmd/gapit/report.go @@ -32,13 +32,7 @@ import ( type reportVerb struct{ ReportFlags } func init() { - verb := &reportVerb{ - ReportFlags: ReportFlags{ - CommandFilterFlags: CommandFilterFlags{ - Context: -1, - }, - }, - } + verb := &reportVerb{} app.AddVerb(&app.Verb{ Name: "report", ShortHelp: "Check a capture replays without issues", @@ -87,18 +81,13 @@ func (verb *reportVerb) Run(ctx context.Context, flags flag.FlagSet) error { return err } - filter, err := verb.commandFilter(ctx, client, capturePath) - if err != nil { - return log.Err(ctx, err, "Failed to build the CommandFilter") - } - boxedCommands, err := client.Get(ctx, capturePath.Commands().Path(), nil) if err != nil { return log.Err(ctx, err, "Failed to acquire the capture's commands") } commands := boxedCommands.(*service.Commands).List - boxedReport, err := client.Get(ctx, capturePath.Report(device, filter, verb.DisplayToSurface).Path(), nil) + boxedReport, err := client.Get(ctx, capturePath.Report(device, verb.DisplayToSurface).Path(), nil) if err != nil { return log.Err(ctx, err, "Failed to acquire the capture's report") } diff --git a/cmd/gapit/screenshot.go b/cmd/gapit/screenshot.go index 91f5754d95..ca4894f1a0 100644 --- a/cmd/gapit/screenshot.go +++ b/cmd/gapit/screenshot.go @@ -20,14 +20,16 @@ import ( "fmt" "image" "image/png" + "math" "os" + "strconv" "strings" "sync" "github.com/google/gapid/core/app" "github.com/google/gapid/core/app/flags" "github.com/google/gapid/core/log" - "github.com/google/gapid/gapis/api" + "github.com/google/gapid/gapis/client" "github.com/google/gapid/gapis/service" "github.com/google/gapid/gapis/service/path" @@ -75,6 +77,11 @@ func (verb *screenshotVerb) Run(ctx context.Context, flags flag.FlagSet) error { for _, at := range verb.At { commands = append(commands, capture.Command(at[0], at[1:]...)) } + } else if verb.ExecutedDraws > 0 { + commands, err = verb.executedDrawCommands(ctx, capture, client, verb.ExecutedDraws) + if err != nil { + return err + } } else { commands, err = verb.frameCommands(ctx, capture, client) if err != nil { @@ -91,8 +98,8 @@ func (verb *screenshotVerb) Run(ctx context.Context, flags flag.FlagSet) error { go func(idx int, command *path.Command) { defer wg.Done() - var err error - if frame, err := verb.getSingleFrame(ctx, command, device, client); err == nil { + frame, err := verb.getSingleFrame(ctx, command, device, client) + if err == nil { err = verb.writeSingleFrame(flipImg(frame), formatOut(verb.Out, idx, multi)) } c <- err @@ -133,26 +140,35 @@ func (verb *screenshotVerb) writeSingleFrame(frame image.Image, fn string) error func (verb *screenshotVerb) getSingleFrame(ctx context.Context, cmd *path.Command, device *path.Device, client service.Service) (*image.NRGBA, error) { ctx = log.V{"cmd": cmd.Indices}.Bind(ctx) - settings := &service.RenderSettings{MaxWidth: uint32(0xFFFFFFFF), MaxHeight: uint32(0xFFFFFFFF)} + reqW, reqH, err := verb.getSize(ctx) + if err != nil { + return nil, log.Errf(ctx, err, "Invalid size flag") + } + settings := &path.RenderSettings{ + MaxWidth: reqW, + MaxHeight: reqH, + DisableReplayOptimization: verb.NoOpt, + DisplayToSurface: verb.DisplayToSurface, + } if verb.Overdraw { - settings.DrawMode = service.DrawMode_OVERDRAW + settings.DrawMode = path.DrawMode_OVERDRAW } - attachment, err := verb.getAttachment(ctx) + attachment, err := verb.getAttachment(ctx, cmd, device, client) if err != nil { return nil, log.Errf(ctx, err, "Get color attachment failed") } - iip, err := client.GetFramebufferAttachment(ctx, - &service.ReplaySettings{ - Device: device, - DisableReplayOptimization: verb.NoOpt, - DisplayToSurface: verb.DisplayToSurface, - }, - cmd, attachment, settings, nil) + fbPath := &path.FramebufferAttachment{ + After: cmd, + Index: attachment, + RenderSettings: settings, + Hints: nil, + } + iip, err := client.Get(ctx, fbPath.Path(), &path.ResolveConfig{ReplayDevice: device}) if err != nil { return nil, log.Errf(ctx, err, "GetFramebufferAttachment failed") } - iio, err := client.Get(ctx, iip.Path(), nil) + iio, err := client.Get(ctx, iip.(*service.FramebufferAttachment).GetImageInfo().Path(), nil) if err != nil { return nil, log.Errf(ctx, err, "Get frame image.Info failed") } @@ -188,59 +204,113 @@ func (verb *screenshotVerb) getSingleFrame(ctx context.Context, cmd *path.Comman }, nil } -func (verb *screenshotVerb) frameCommands(ctx context.Context, capture *path.Capture, client service.Service) ([]*path.Command, error) { +func (verb *screenshotVerb) frameCommands(ctx context.Context, capture *path.Capture, client client.Client) ([]*path.Command, error) { filter, err := verb.CommandFilterFlags.commandFilter(ctx, client, capture) if err != nil { return nil, log.Err(ctx, err, "Couldn't get filter") } + filter.OnlyEndOfFrames = true - requestEvents := path.Events{ - Capture: capture, - LastInFrame: true, - DrawCalls: verb.Draws, - Filter: filter, - } + treePath := capture.CommandTree(filter) - // Get the end-of-frame and possibly draw call events. - events, err := getEvents(ctx, client, &requestEvents) + boxedTree, err := client.Get(ctx, treePath.Path(), nil) if err != nil { - return nil, log.Err(ctx, err, "Couldn't get frame events") + return nil, log.Err(ctx, err, "Failed to load the command tree") } - // Compute an index of frame to event idx. - frameIdx := map[int]int{} - lastFrame := 0 - for i, e := range events { - if e.Kind == service.EventKind_LastInFrame { - lastFrame++ - frameIdx[lastFrame] = i + tree := boxedTree.(*service.CommandTree) + + var allEOFCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil } - } + allEOFCommands = append(allEOFCommands, n.Commands.First()) + return nil + }, "", true) if len(verb.Frame) == 0 { - verb.Frame = []int{lastFrame} + verb.Frame = []int{len(allEOFCommands) - 1} } - var commands []*path.Command + var retCommands []*path.Command for _, frame := range verb.Frame { - last, ok := frameIdx[frame] - if !ok { - return nil, fmt.Errorf("Invalid frame number %d (last frame is %d)", frame, lastFrame) + + if frame < 0 || frame >= len(allEOFCommands) { + return nil, fmt.Errorf("Invalid frame number %d (last frame is %d)", frame, len(allEOFCommands)) } - first := last if verb.Draws { - if frame == 1 { - first = 0 - } else { - first = frameIdx[frame-1] + + firstFrameCommand := uint64(0) + if frame > 0 { + firstFrameCommand = allEOFCommands[frame-1].Indices[0] } + + lastFrameCommand := uint64(math.MaxUint64) + if frame < len(allEOFCommands) { + lastFrameCommand = allEOFCommands[frame].Indices[0] + } + + drawCommands, err := verb.doExecutedDrawCommands(ctx, capture, client, math.MaxInt, firstFrameCommand, lastFrameCommand) + if err != nil { + return nil, fmt.Errorf("Error getting draw calls for frame") + } + + retCommands = append(retCommands, drawCommands...) + } else { + retCommands = append(retCommands, allEOFCommands[frame]) + } + } + + return retCommands, nil +} + +func (verb *screenshotVerb) executedDrawCommands(ctx context.Context, capture *path.Capture, client client.Client, maxAmount int) ([]*path.Command, error) { + return verb.doExecutedDrawCommands(ctx, capture, client, maxAmount, 0, math.MaxUint64) +} + +func (verb *screenshotVerb) doExecutedDrawCommands(ctx context.Context, capture *path.Capture, client client.Client, maxAmount int, after uint64, before uint64) ([]*path.Command, error) { + filter, err := verb.CommandFilterFlags.commandFilter(ctx, client, capture) + if err != nil { + return nil, log.Err(ctx, err, "Couldn't get filter") + } + filter.OnlyExecutedDraws = true + + treePath := capture.CommandTree(filter) + + boxedTree, err := client.Get(ctx, treePath.Path(), nil) + if err != nil { + return nil, log.Err(ctx, err, "Failed to load the command tree") + } + + tree := boxedTree.(*service.CommandTree) + + var allDrawCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + // Filter out queue submits, which either have children or singular command indices + if n.Group != "" || n.NumChildren > 0 || len(n.Commands.First().Indices) == 1 { + return nil } - for idx := first; idx <= last; idx++ { - commands = append(commands, events[idx].Command) + if n.Commands.First().Indices[0] >= after && n.Commands.First().Indices[0] < before { + allDrawCommands = append(allDrawCommands, n.Commands.First()) } + return nil + }, "", true) + + if len(allDrawCommands) > maxAmount { + commands := make([]*path.Command, maxAmount) + // We use a float step so that we have a fairly even distribution when ratio < 2 (e.g. 5/3) + step := float32(len(allDrawCommands)) / float32(maxAmount) + + for i := 0; i < len(commands); i++ { + commands[i] = allDrawCommands[int32(float32(i)*step)] + } + + return commands, nil + } else { + return allDrawCommands, nil } - return commands, nil } // rescaleBytes scales the values in `data` from [0, `max`] to [0, 255]. If @@ -267,21 +337,50 @@ func rescaleBytes(ctx context.Context, data []byte, max int) { } } -func (verb *screenshotVerb) getAttachment(ctx context.Context) (api.FramebufferAttachment, error) { - switch strings.ToLower(verb.Attachment) { - case "", "color", "color0", "c0", "c", "0": - return api.FramebufferAttachment_Color0, nil - case "color1", "c1", "1": - return api.FramebufferAttachment_Color1, nil - case "color2", "c2", "2": - return api.FramebufferAttachment_Color2, nil - case "color3", "c3", "3": - return api.FramebufferAttachment_Color3, nil - case "depth", "d": - return api.FramebufferAttachment_Depth, nil - case "stencil", "s": - return api.FramebufferAttachment_Stencil, nil - default: - return 0, log.Errf(ctx, nil, "Invalid color attachment %v", verb.Attachment) +func (verb *screenshotVerb) getAttachment(ctx context.Context, cmd *path.Command, device *path.Device, client service.Service) (uint32, error) { + if verb.Attachment == "" { + fbsPath := &path.FramebufferAttachments{ + After: cmd, + } + fbs, err := client.Get(ctx, fbsPath.Path(), &path.ResolveConfig{ReplayDevice: device}) + if err != nil { + return 0, log.Errf(ctx, err, "GetFramebufferAttachments failed at cmd %v", cmd) + } + attachments := fbs.(*service.FramebufferAttachments).GetAttachments() + if len(attachments) == 0 { + return 0, log.Errf(ctx, err, "No Framebuffer Attachments") + } + + return attachments[0].GetIndex(), nil + } + + // TODO: Add-back ability to type "depth" to get the depth attachment + i, err := strconv.ParseUint(verb.Attachment, 10, 32) + if err != nil { + return 0, log.Errf(ctx, nil, "Invalid attachment %v", verb.Attachment) + } + return uint32(i), nil +} + +func (verb *screenshotVerb) getSize(ctx context.Context) (w, h uint32, err error) { + if verb.Size == "" { + w = uint32(0xFFFFFFFF) + h = uint32(0xFFFFFFFF) + return + } + var s int + if p := strings.Index(verb.Size, "x"); p != -1 { + s, err = strconv.Atoi(verb.Size[0:p]) + w = uint32(s) + if err == nil { + s, err = strconv.Atoi(verb.Size[p+1:]) + h = uint32(s) + } + return + } else { + s, err = strconv.Atoi(verb.Size) + w = uint32(s) + h = uint32(s) + return } } diff --git a/cmd/gapit/server_performance.go b/cmd/gapit/server_performance.go new file mode 100644 index 0000000000..9d5c3ce5df --- /dev/null +++ b/cmd/gapit/server_performance.go @@ -0,0 +1,86 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +package main + +import ( + "context" + "flag" + "time" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" + "github.com/google/gapid/gapis/service" + gapidPath "github.com/google/gapid/gapis/service/path" +) + +type serverPerformanceVerb struct{ ServerPerformanceFlags } + +func init() { + verb := &serverPerformanceVerb{} + app.AddVerb(&app.Verb{ + Name: "server_performance", + ShortHelp: "Test the performance of GAPIS replay generation.", + Action: verb, + }) +} + +func (verb *serverPerformanceVerb) Run(ctx context.Context, flags flag.FlagSet) error { + + if flags.NArg() != 1 { + app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg()) + return nil + } + + client, capturePath, err := getGapisAndLoadCapture(ctx, verb.Gapis, verb.Gapir, flags.Arg(0), verb.CaptureFileFlags) + if err != nil { + return err + } + defer client.Close() + + var device *gapidPath.Device + if !verb.OriginalDevice { + device, err = getDevice(ctx, client, capturePath, verb.Gapir) + if err != nil { + return err + } + } + + opts := &service.ExportReplayOptions{ + FramebufferAttachments: make([]*gapidPath.FramebufferAttachment, 0), + GetTimestampsRequest: nil, + DisplayToSurface: false, + } + + start := time.Now() + if err := client.ExportReplay(ctx, capturePath, device, "replay_export", opts); err != nil { + return log.Err(ctx, err, "Failed to export replay") + } + elapsed := time.Since(start) + + startRepeat := time.Now() + if err := client.ExportReplay(ctx, capturePath, device, "replay_export", opts); err != nil { + return log.Err(ctx, err, "Failed to export replay") + } + elapsedRepeat := time.Since(startRepeat) + + log.W(ctx, "\n\n\n\n") + log.W(ctx, "--------------------------------") + log.W(ctx, "RESULTS:") + log.W(ctx, "First time replay generation:\t%s\t", elapsed) + log.W(ctx, "Repeat replay generation:\t%s\t", elapsedRepeat) + log.W(ctx, "--------------------------------\n\n\n\n") + + return nil +} diff --git a/cmd/gapit/split.go b/cmd/gapit/split.go new file mode 100644 index 0000000000..8b7de9a7d4 --- /dev/null +++ b/cmd/gapit/split.go @@ -0,0 +1,60 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +package main + +import ( + "context" + "flag" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" +) + +type splitVerb struct{ SplitFlags } + +func init() { + verb := &splitVerb{} + + app.AddVerb(&app.Verb{ + Name: "split", + ShortHelp: "Splits traces by carving out subranges into new traces", + Action: verb, + }) +} + +func (verb *splitVerb) Run(ctx context.Context, flags flag.FlagSet) error { + if flags.NArg() != 1 { + app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg()) + return nil + } + + client, capture, err := getGapisAndLoadCapture(ctx, verb.Gapis, verb.Gapir, flags.Arg(0), verb.CaptureFileFlags) + if err != nil { + return err + } + defer client.Close() + + newCapture, err := client.SplitCapture(ctx, capture.CommandRange(verb.From, verb.To)) + if err != nil { + return err + } + log.I(ctx, "Created new capture; id: %s", newCapture.ID) + + output := verb.Out + if output == "" { + output = "split.gfxtrace" + } + return client.SaveCapture(ctx, newCapture, output) +} diff --git a/cmd/gapit/status.go b/cmd/gapit/status.go index 738e9296f9..06d777180b 100644 --- a/cmd/gapit/status.go +++ b/cmd/gapit/status.go @@ -96,6 +96,18 @@ type tsk struct { children map[uint64]*tsk } +type u64List []uint64 + +// Len is the number of elements in the collection. +func (s u64List) Len() int { return len(s) } + +// Less reports whether the element with +// index i should sort before the element with index j. +func (s u64List) Less(i, j int) bool { return s[i] < s[j] } + +// Swap swaps the elements with indexes i and j. +func (s u64List) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + func (verb *statusVerb) Run(ctx context.Context, flags flag.FlagSet) error { client, err := getGapis(ctx, verb.Gapis, GapirFlags{}) if err != nil { @@ -114,8 +126,6 @@ func (verb *statusVerb) Run(ctx context.Context, flags flag.FlagSet) error { totalBlocked := 0 currentMemoryUsage := uint64(0) maxMemoryUsage := uint64(0) - replayTotalInstrs := uint32(0) - replayFinishedInstrs := uint32(0) var findTask func(map[uint64]*tsk, []uint64) *tsk @@ -302,8 +312,6 @@ func (verb *statusVerb) Run(ctx context.Context, flags flag.FlagSet) error { } currentMemoryUsage = tu.TotalHeap }, func(tu *service.ReplayUpdate) { - replayTotalInstrs = tu.TotalInstrs - replayFinishedInstrs = tu.FinishedInstrs }) ec <- err }) diff --git a/cmd/gapit/sxs_video.go b/cmd/gapit/sxs_video.go index 874c2c8190..a47e23afd1 100644 --- a/cmd/gapit/sxs_video.go +++ b/cmd/gapit/sxs_video.go @@ -31,6 +31,7 @@ import ( "github.com/google/gapid/core/math/f32" "github.com/google/gapid/core/math/sint" "github.com/google/gapid/core/text/reflow" + "github.com/google/gapid/gapis/client" "github.com/google/gapid/gapis/service" "github.com/google/gapid/gapis/service/path" ) @@ -74,91 +75,64 @@ func getFBO(ctx context.Context, client service.Service, a *path.Command) (*img. func (verb *videoVerb) sxsVideoSource( ctx context.Context, capture *path.Capture, - client service.Service, + client client.Client, device *path.Device) (videoFrameWriter, error) { filter, err := verb.CommandFilterFlags.commandFilter(ctx, client, capture) if err != nil { return nil, log.Err(ctx, err, "Couldn't get filter") } + filter.OnlyFramebufferObservations = true - // Get the draw call and end-of-frame events. - events, err := getEvents(ctx, client, &path.Events{ - Capture: capture, - DrawCalls: true, - Clears: true, - LastInFrame: true, - FramebufferObservations: true, - Filter: filter, - }) + treePath := capture.CommandTree(filter) + + boxedTree, err := client.Get(ctx, treePath.Path(), nil) if err != nil { - return nil, log.Err(ctx, err, "Couldn't get events") + return nil, log.Err(ctx, err, "Failed to load the command tree") } + tree := boxedTree.(*service.CommandTree) + + var allFBOCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil + } + allFBOCommands = append(allFBOCommands, n.Commands.First()) + return nil + }, "", true) + // Find maximum frame width / height of all frames, and get all observation // command indices. videoFrames := []*videoFrame{} w, h := 0, 0 frameIndex, numDrawCalls := 0, 0 - // Some traces call eglSwapBuffers without actually drawing to or clearing - // the framebuffer. This is most common during loading screens. - // These would result in a failed comparison as the observed frame could - // be anything and the replayed frame will show the undefined framebuffer - // pattern. - // Permit the first run of frames to have no content. If there are no - // draw-calls or clear calls at all however, then do not permit this. - permitNoMatch := false - - for _, e := range events { - if e.Kind == service.EventKind_Clear || - e.Kind == service.EventKind_DrawCall { - permitNoMatch = true - break + for _, cmd := range allFBOCommands { + + fbo, err := getFBO(ctx, client, cmd) + if err != nil { + return nil, err + } + if int(fbo.Width) > w { + w = int(fbo.Width) + } + if int(fbo.Height) > h { + h = int(fbo.Height) } - } - var lastFrameEvent *path.Command - for _, e := range events { - switch e.Kind { - case service.EventKind_FramebufferObservation: - // We assume FBO events come after other events on a command. - if lastFrameEvent == nil { - log.W(ctx, "Got framebuffer observation but nothing wrote to the frame") - continue - } - fbo, err := getFBO(ctx, client, e.Command) - if err != nil { - return nil, err - } - if int(fbo.Width) > w { - w = int(fbo.Width) - } - if int(fbo.Height) > h { - h = int(fbo.Height) - } + videoFrames = append(videoFrames, &videoFrame{ + fbo: fbo, + fboIndex: fmt.Sprint(cmd.Indices), + frameIndex: frameIndex, + numDrawCalls: numDrawCalls, + command: cmd, + permitNoMatch: false, + }) - videoFrames = append(videoFrames, &videoFrame{ - fbo: fbo, - fboIndex: fmt.Sprint(e.Command.Indices), - frameIndex: frameIndex, - numDrawCalls: numDrawCalls, - command: lastFrameEvent, - permitNoMatch: permitNoMatch, - }) - case service.EventKind_Clear: - permitNoMatch = false - lastFrameEvent = e.Command - case service.EventKind_DrawCall: - permitNoMatch = false - lastFrameEvent = e.Command - numDrawCalls++ - case service.EventKind_LastInFrame: - lastFrameEvent = e.Command - frameIndex++ - numDrawCalls = 0 - } + frameIndex++ } + if verb.Frames.Minimum > len(videoFrames) { return nil, log.Errf(ctx, nil, "Captured only %v frames, require %v frames at minimum", len(videoFrames), verb.Frames.Minimum) } @@ -224,7 +198,7 @@ func (verb *videoVerb) sxsVideoSource( // ┃ ┃ Histogram ┃ // ┃ ┣━━━━━━━━━━━━━━━┫ p7 // ┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━┛ - // p6 + // p6 p8 var histogramHeight int if histogram != nil { histogramHeight = histogram.Bounds().Dy() @@ -234,8 +208,10 @@ func (verb *videoVerb) sxsVideoSource( p5 := image.Pt(w+2, h+200) p6 := image.Pt(w, h*2) p7 := image.Pt(w*2, p5.Y+histogramHeight) + p8 := image.Pt(w*2, h*2) white := &image.Uniform{C: color.White} + black := &image.Uniform{C: color.Black} rect := func(min, max image.Point) image.Rectangle { return image.Rectangle{Min: min, Max: max} } @@ -262,6 +238,7 @@ func (verb *videoVerb) sxsVideoSource( draw.Draw(sxs, rect(p2, p6), d, image.ZP, draw.Src) } + draw.Draw(sxs, rect(p3, p8), black, image.ZP, draw.Src) // Histogram if h := histogram; h != nil { draw.Draw(sxs, rect(p5, p7), histogram, image.ZP, draw.Src) @@ -347,6 +324,11 @@ func getHistogram(videoFrames []*videoFrame) *image.NRGBA { pixels := make([]byte, w*h*4) out := &image.NRGBA{Pix: pixels, Stride: w * 4, Rect: image.Rect(0, 0, w, h)} + for i := range pixels { + if i%4 == 3 { + pixels[i] = 255 + } + } // Layout into RGBA32 bitmap. f32s := make([]float32, bins*w) diff --git a/cmd/gapit/trace.go b/cmd/gapit/trace.go index ae4e118f7e..d65c191542 100644 --- a/cmd/gapit/trace.go +++ b/cmd/gapit/trace.go @@ -41,7 +41,7 @@ type traceVerb struct{ TraceFlags } func init() { verb := &traceVerb{} - verb.TraceFlags.Disable.PCS = true + verb.Disable.Unknown.Extensions = true app.AddVerb(&app.Verb{ Name: "trace", @@ -62,7 +62,7 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { app.Usage(ctx, "The API is required.") return nil } - api, err := verb.apiAndType() + api, err := verb.traceType() if err != nil { return err } @@ -70,7 +70,8 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { traceURI := verb.URI if traceURI == "" && verb.Local.Port == 0 { if flags.NArg() != 1 { - if api.traceType != service.TraceType_Perfetto { + if api.traceType != service.TraceType_Perfetto && + api.traceType != service.TraceType_Fuchsia { app.Usage(ctx, "Expected application name.") return nil } @@ -227,26 +228,26 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { } options := &service.TraceOptions{ - Type: api.traceType, - Apis: api.apis, - AdditionalCommandLineArgs: verb.AdditionalArgs, - Cwd: verb.WorkingDir, - Environment: verb.Env, - Duration: float32(verb.For.Seconds()), - ObserveFrameFrequency: uint32(verb.Observe.Frames), - ObserveDrawFrequency: uint32(verb.Observe.Draws), - StartFrame: uint32(verb.Start.At.Frame), - FramesToCapture: uint32(verb.Capture.Frames), - DisablePcs: verb.Disable.PCS, - RecordErrorState: verb.Record.Errors, - DeferStart: verb.Start.Defer, - NoBuffer: verb.No.Buffer, - HideUnknownExtensions: verb.Disable.Unknown.Extensions, - RecordTraceTimes: verb.Record.TraceTimes, - ClearCache: verb.Clear.Cache, - ServerLocalSavePath: out, - PipeName: verb.PipeName, - DisableCoherentMemoryTracker: verb.Disable.CoherentMemoryTracker, + Type: api.traceType, + AdditionalCommandLineArgs: verb.AdditionalArgs, + Cwd: verb.WorkingDir, + Environment: verb.Env, + Duration: float32(verb.For.Seconds()), + ObserveFrameFrequency: uint32(verb.Observe.Frames), + StartFrame: uint32(verb.Start.At.Frame), + FramesToCapture: uint32(verb.Capture.Frames), + DeferStart: verb.Start.Defer, + IgnoreFrameBoundaryDelimiters: !verb.UseAndroidFrameBoundary, + NoBuffer: verb.No.Buffer, + HideUnknownExtensions: verb.Disable.Unknown.Extensions, + RecordTraceTimes: verb.Record.TraceTimes, + ClearCache: verb.Clear.Cache, + ServerLocalSavePath: out, + PipeName: verb.PipeName, + DisableCoherentMemoryTracker: verb.Disable.CoherentMemoryTracker, + WaitForDebugger: verb.WaitForDebugger, + ProcessName: verb.ProcessName, + LoadValidationLayer: verb.LoadValidationLayer, } target(options) @@ -264,7 +265,6 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { dur = 10 * 60 * 1000 } options.PerfettoConfig.DurationMs = proto.Uint32(dur) - options.Duration = 0 } handler, err := client.Trace(ctx) @@ -283,8 +283,7 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { } log.I(ctx, "Trace Status %+v", status) - handlerInstalled := options.Duration > 0 - + handlerInstalled := false return task.Retry(ctx, 0, time.Second*3, func(ctx context.Context) (retry bool, err error) { status, err = handler.Event(ctx, service.TraceEvent_Status) if err == io.EOF { @@ -322,33 +321,34 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { }) } -type apiAndType struct { +type traceType struct { traceType service.TraceType - apis []string traceExt string } -func (verb *traceVerb) apiAndType() (apiAndType, error) { +func (verb *traceVerb) traceType() (traceType, error) { switch verb.API { - case "vulkan": - return apiAndType{ - service.TraceType_Graphics, - []string{"Vulkan"}, + case "angle": + return traceType{ + service.TraceType_ANGLE, ".gfxtrace", }, nil - case "gles": - return apiAndType{ + case "vulkan": + return traceType{ service.TraceType_Graphics, - []string{"OpenGLES"}, ".gfxtrace", }, nil case "perfetto": - return apiAndType{ + return traceType{ service.TraceType_Perfetto, - []string{}, ".perfetto", }, nil + case "fuchsia": + return traceType{ + service.TraceType_Fuchsia, + ".fxt", + }, nil default: - return apiAndType{}, fmt.Errorf("Unknown API '%s'", verb.API) + return traceType{}, fmt.Errorf("Unknown API '%s'", verb.API) } } diff --git a/cmd/gapit/trim.go b/cmd/gapit/trim.go index 7eacf17f1a..fdfb6c5e3f 100644 --- a/cmd/gapit/trim.go +++ b/cmd/gapit/trim.go @@ -21,6 +21,7 @@ import ( "github.com/google/gapid/core/app" "github.com/google/gapid/core/log" + "github.com/google/gapid/gapis/client" "github.com/google/gapid/gapis/service" "github.com/google/gapid/gapis/service/path" ) @@ -50,12 +51,12 @@ func (verb *trimVerb) Run(ctx context.Context, flags flag.FlagSet) error { } defer client.Close() - eofEvents, err := verb.eofEvents(ctx, capture, client) + eofCommands, err := verb.eofCommands(ctx, capture, client) if err != nil { return err } - dceRequest := verb.getDCERequest(eofEvents, capture) + dceRequest := verb.getDCERequest(eofCommands, capture) if len(dceRequest) > 0 { capture, err = client.DCECapture(ctx, capture, dceRequest) if err != nil { @@ -78,47 +79,50 @@ func (verb *trimVerb) Run(ctx context.Context, flags flag.FlagSet) error { return nil } -func (verb *trimVerb) eofEvents(ctx context.Context, capture *path.Capture, client service.Service) ([]*service.Event, error) { +func (verb *trimVerb) eofCommands(ctx context.Context, capture *path.Capture, client client.Client) ([]*path.Command, error) { filter, err := verb.CommandFilterFlags.commandFilter(ctx, client, capture) if err != nil { return nil, log.Err(ctx, err, "Couldn't get filter") } - requestEvents := path.Events{ - Capture: capture, - LastInFrame: true, - Filter: filter, - } + filter.OnlyEndOfFrames = true - if verb.Commands { - requestEvents.LastInFrame = false - requestEvents.AllCommands = true - } + treePath := capture.CommandTree(filter) - // Get the end-of-frame events. - eofEvents, err := getEvents(ctx, client, &requestEvents) + boxedTree, err := client.Get(ctx, treePath.Path(), nil) if err != nil { - return nil, log.Err(ctx, err, "Couldn't get frame events") + return nil, log.Err(ctx, err, "Failed to load the command tree") } - lastFrame := verb.Frames.Start + tree := boxedTree.(*service.CommandTree) + + var eofCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil + } + eofCommands = append(eofCommands, n.Commands.First()) + return nil + }, "", true) + + lastFrame := len(eofCommands) - 1 if verb.Frames.Count > 0 { lastFrame += verb.Frames.Count - 1 } - if lastFrame >= len(eofEvents) { - return nil, log.Errf(ctx, nil, "Requested frame %d, but capture only contains %d frames", lastFrame, len(eofEvents)) + if lastFrame >= len(eofCommands) { + return nil, log.Errf(ctx, nil, "Requested frame %d, but capture only contains %d frames", lastFrame, len(eofCommands)) } - return eofEvents, nil + return eofCommands, nil } -func (verb *trimVerb) getDCERequest(eofEvents []*service.Event, p *path.Capture) []*path.Command { +func (verb *trimVerb) getDCERequest(eofCommands []*path.Command, p *path.Capture) []*path.Command { frameCount := verb.Frames.Count if frameCount < 0 { - frameCount = len(eofEvents) - verb.Frames.Start + frameCount = len(eofCommands) - verb.Frames.Start } dceRequest := make([]*path.Command, 0, frameCount+len(verb.ExtraCommands)) for i := 0; i < frameCount; i++ { - indices := eofEvents[verb.Frames.Start+i].Command.Indices + indices := eofCommands[verb.Frames.Start+i].Indices newIndices := make([]uint64, len(indices)) copy(newIndices, indices) cmd := &path.Command{ diff --git a/cmd/gapit/trim_state.go b/cmd/gapit/trim_state.go new file mode 100644 index 0000000000..888649c6e9 --- /dev/null +++ b/cmd/gapit/trim_state.go @@ -0,0 +1,67 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package main + +import ( + "context" + "flag" + "io/ioutil" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" +) + +type trimStateVerb struct{ TrimStateFlags } + +func init() { + verb := &trimStateVerb{} + app.AddVerb(&app.Verb{ + Name: "trim_state", + ShortHelp: "Trims a gfx trace's initial state to the resources actually used by the commands", + Action: verb, + }) +} + +func (verb *trimStateVerb) Run(ctx context.Context, flags flag.FlagSet) error { + if flags.NArg() != 1 { + app.Usage(ctx, "Exactly one gfx trace file expected, got %d", flags.NArg()) + return nil + } + + client, capture, err := getGapisAndLoadCapture(ctx, verb.Gapis, verb.Gapir, flags.Arg(0), verb.CaptureFileFlags) + if err != nil { + return err + } + defer client.Close() + + c, err := client.TrimCaptureInitialState(ctx, capture) + if err != nil { + return log.Errf(ctx, err, "TrimCaptureInitialState(%v)", capture) + } + + data, err := client.ExportCapture(ctx, c) + if err != nil { + return log.Errf(ctx, err, "ExportCapture(%v)", c) + } + + output := verb.Out + if output == "" { + output = "initialStateTrimmed.gfxtrace" + } + if err := ioutil.WriteFile(output, data, 0666); err != nil { + return log.Errf(ctx, err, "Writing file: %v", output) + } + return nil +} diff --git a/cmd/gapit/validate_gpu_profiling.go b/cmd/gapit/validate_gpu_profiling.go index 0ea394dc95..5061dc9403 100644 --- a/cmd/gapit/validate_gpu_profiling.go +++ b/cmd/gapit/validate_gpu_profiling.go @@ -51,9 +51,13 @@ func (verb *validateGpuProfilingVerb) Run(ctx context.Context, flags flag.FlagSe someDeviceFailed := false for i, p := range devices { fmt.Fprintf(stdout, "-- Device %v: %v --\n", i, p.ID.ID()) - err = client.ValidateDevice(ctx, p) + res, err := client.ValidateDevice(ctx, p) if err != nil { - fmt.Fprintf(stdout, "%v\n", log.Err(ctx, err, "Failed to validate device")) + fmt.Fprintf(stdout, "%v\n", log.Errf(ctx, err, "Failed to start device validation: %s", err)) + someDeviceFailed = true + continue + } else if len(res.ValidationFailureMsg) > 0 { + fmt.Fprintf(stdout, "%v\n", log.Errf(ctx, nil, "Device validation failed: %s, trace file: %s", res.ValidationFailureMsg, res.TracePath)) someDeviceFailed = true continue } diff --git a/cmd/gapit/video.go b/cmd/gapit/video.go index 2905e581da..378c5d13fe 100644 --- a/cmd/gapit/video.go +++ b/cmd/gapit/video.go @@ -38,7 +38,7 @@ import ( "github.com/google/gapid/core/os/file" "github.com/google/gapid/core/text/reflow" "github.com/google/gapid/core/video" - "github.com/google/gapid/gapis/api" + "github.com/google/gapid/gapis/client" "github.com/google/gapid/gapis/service" "github.com/google/gapid/gapis/service/path" @@ -68,13 +68,13 @@ func init() { } type videoFrameWriter func(chan<- image.Image) error -type videoSource func(ctx context.Context, capture *path.Capture, client service.Service, device *path.Device) (videoFrameWriter, error) +type videoSource func(ctx context.Context, capture *path.Capture, client client.Client, device *path.Device) (videoFrameWriter, error) type videoSink func(ctx context.Context, filepath string, vidFun videoFrameWriter) error func (verb *videoVerb) regularVideoSource( ctx context.Context, capture *path.Capture, - client service.Service, + client client.Client, device *path.Device) (videoFrameWriter, error) { filter, err := verb.CommandFilterFlags.commandFilter(ctx, client, capture) @@ -82,36 +82,41 @@ func (verb *videoVerb) regularVideoSource( return nil, log.Err(ctx, err, "Couldn't get filter") } - requestEvents := path.Events{ - Capture: capture, - LastInFrame: true, - Filter: filter, + if verb.Commands == false { + filter.OnlyEndOfFrames = true } - if verb.Commands { - requestEvents.LastInFrame = false - requestEvents.AllCommands = true - } + treePath := capture.CommandTree(filter) - // Get the end-of-frame events. - eofEvents, err := getEvents(ctx, client, &requestEvents) + boxedTree, err := client.Get(ctx, treePath.Path(), nil) if err != nil { - return nil, log.Err(ctx, err, "Couldn't get frame events") + return nil, log.Err(ctx, err, "Failed to load the command tree") } - if verb.Frames.Minimum > len(eofEvents) { - return nil, log.Errf(ctx, nil, "Captured only %v frames, requires %v frames at minimum", len(eofEvents), verb.Frames.Minimum) + tree := boxedTree.(*service.CommandTree) + + var eofCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil + } + eofCommands = append(eofCommands, n.Commands.First()) + return nil + }, "", true) + + if verb.Frames.Minimum > len(eofCommands) { + return nil, log.Errf(ctx, nil, "Captured only %v frames, requires %v frames at minimum", len(eofCommands), verb.Frames.Minimum) } - if verb.Frames.Start < len(eofEvents) { - eofEvents = eofEvents[verb.Frames.Start:] + if verb.Frames.Start < len(eofCommands) { + eofCommands = eofCommands[verb.Frames.Start:] } - if verb.Frames.Count != allTheWay && verb.Frames.Count < len(eofEvents) { - eofEvents = eofEvents[:verb.Frames.Count] + if verb.Frames.Count != allTheWay && verb.Frames.Count < len(eofCommands) { + eofCommands = eofCommands[:verb.Frames.Count] } - frameCount := len(eofEvents) + frameCount := len(eofCommands) log.I(ctx, "Frames: %d", frameCount) @@ -126,10 +131,10 @@ func (verb *videoVerb) regularVideoSource( errors := make([]error, frameCount) var errorCount uint32 - for i, e := range eofEvents { + for i, e := range eofCommands { i, e := i, e executor(ctx, func(ctx context.Context) error { - if frame, err := getFrame(ctx, verb.Max.Width, verb.Max.Height, e.Command, device, client, verb.NoOpt); err == nil { + if frame, err := getFrame(ctx, verb.Max.Width, verb.Max.Height, e, device, client, verb.NoOpt); err == nil { rendered[i] = flipImg(frame) } else { errors[i] = err @@ -141,7 +146,7 @@ func (verb *videoVerb) regularVideoSource( events.Wait(ctx) if errorCount > 0 { - log.W(ctx, "%d/%d frames errored", errorCount, len(eofEvents)) + log.W(ctx, "%d/%d frames errored", errorCount, len(eofCommands)) } // Get the max width and height @@ -166,7 +171,7 @@ func (verb *videoVerb) regularVideoSource( return func(frames chan<- image.Image) error { for i, frame := range rendered { if err := errors[i]; err != nil { - log.E(ctx, "Error getting frame at %v: %v", eofEvents[i].Command, err) + log.E(ctx, "Error getting frame at %v: %v", eofCommands[i], err) continue } @@ -179,7 +184,7 @@ func (verb *videoVerb) regularVideoSource( sb := new(bytes.Buffer) refw := reflow.New(sb) fmt.Fprint(refw, verb.Text) - fmt.Fprintf(refw, "Frame: %d, cmd: %v", i, eofEvents[i].Command.Indices) + fmt.Fprintf(refw, "Frame: %d, cmd: %v", i, eofCommands[i].Indices) refw.Flush() str := sb.String() font.DrawString(str, frame, image.Pt(4, 4), color.Black) @@ -209,14 +214,30 @@ func (verb *videoVerb) Run(ctx context.Context, flags flag.FlagSet) error { return err } - fboEvents, err := getEvents(ctx, client, &path.Events{ - Capture: capture, - FramebufferObservations: true, - }) + filter, err := verb.CommandFilterFlags.commandFilter(ctx, client, capture) + if err != nil { + return log.Err(ctx, err, "Couldn't get filter") + } + filter.OnlyFramebufferObservations = true + + treePath := capture.CommandTree(filter) + + boxedTree, err := client.Get(ctx, treePath.Path(), nil) if err != nil { - return log.Err(ctx, err, "Couldn't get framebuffer observation events") + return log.Err(ctx, err, "Failed to load the command tree") } + tree := boxedTree.(*service.CommandTree) + + var fboCommands []*path.Command + traverseCommandTree(ctx, client, tree.Root, func(n *service.CommandTreeNode, prefix string) error { + if n.Group != "" { + return nil + } + fboCommands = append(fboCommands, n.Commands.First()) + return nil + }, "", true) + var vidSrc videoSource var vidFun videoFrameWriter var vidOut videoSink @@ -228,14 +249,20 @@ func (verb *videoVerb) Run(ctx context.Context, flags flag.FlagSet) error { case RegularVideo: vidSrc = verb.regularVideoSource vidOut = verb.encodeVideo + case SxsFrames: + fallthrough case SxsVideo: - if len(fboEvents) == 0 { + if len(fboCommands) == 0 { return fmt.Errorf("Capture does not contain framebuffer observations") } vidSrc = verb.sxsVideoSource - vidOut = verb.encodeVideo + if verb.Type == SxsFrames { + vidOut = verb.writeFrames + } else { + vidOut = verb.encodeVideo + } case AutoVideo: - if len(fboEvents) > 0 { + if len(fboCommands) > 0 { vidSrc = verb.sxsVideoSource } else { vidSrc = verb.regularVideoSource @@ -257,6 +284,8 @@ func (verb *videoVerb) Run(ctx context.Context, flags flag.FlagSet) error { } return vidOut(ctx, filepath, vidFun) + + return nil } func (verb *videoVerb) writeFrames(ctx context.Context, filepath string, vidFun videoFrameWriter) error { @@ -332,15 +361,18 @@ func (verb *videoVerb) encodeVideo(ctx context.Context, filepath string, vidFun func getFrame(ctx context.Context, maxWidth, maxHeight int, cmd *path.Command, device *path.Device, client service.Service, noOpt bool) (*image.NRGBA, error) { ctx = log.V{"cmd": cmd.Indices}.Bind(ctx) - settings := &service.RenderSettings{MaxWidth: uint32(maxWidth), MaxHeight: uint32(maxHeight)} - iip, err := client.GetFramebufferAttachment(ctx, &service.ReplaySettings{ - Device: device, - DisableReplayOptimization: noOpt, - }, cmd, api.FramebufferAttachment_Color0, settings, nil) + settings := &path.RenderSettings{MaxWidth: uint32(maxWidth), MaxHeight: uint32(maxHeight), DisableReplayOptimization: noOpt} + fbPath := &path.FramebufferAttachment{ + After: cmd, + Index: 0, + RenderSettings: settings, + Hints: nil, + } + iip, err := client.Get(ctx, fbPath.Path(), &path.ResolveConfig{ReplayDevice: device}) if err != nil { return nil, log.Errf(ctx, err, "GetFramebufferAttachment failed at %v", cmd) } - iio, err := client.Get(ctx, iip.Path(), nil) + iio, err := client.Get(ctx, iip.(*service.FramebufferAttachment).GetImageInfo().Path(), nil) if err != nil { return nil, log.Errf(ctx, err, "Get frame image.Info failed at %v", cmd) } diff --git a/cmd/gofuse/main.go b/cmd/gofuse/main.go index 0ccf991f71..c76a87d10a 100644 --- a/cmd/gofuse/main.go +++ b/cmd/gofuse/main.go @@ -16,19 +16,21 @@ // // gofuse will create a new 'fused' directory in the project root which // contains: -// • Symlinks to authored files in the GAPID source tree. -// • Symlinks to bazel-generated files (bazel-out/[config]/{bin,genfiles}). -// • Symlinks to external 3rd-party .go files. +// - Symlinks to authored files in the GAPID source tree. +// - Symlinks to bazel-generated files (bazel-out/[config]/{bin,genfiles}). +// - Symlinks to external 3rd-party .go files. +// // These symlinks are 'fused' into a single, common directory structure that // is expected by the typical GOPATH rules used by go tooling. // // Note: the extensive use of symlinks makes Windows support unlikely. // // Examples: -// bazel run //cmd/gofuse -// bazel run //cmd/gofuse -- --bazelout=k8-fastbuild -// bazel run //cmd/gofuse -- --bazelout=k8-dbg -// bazel run //cmd/gofuse -- --bazelout=darwin-fastbuild +// +// bazel run //cmd/gofuse +// bazel run //cmd/gofuse -- --bazelout=k8-fastbuild +// bazel run //cmd/gofuse -- --bazelout=k8-dbg +// bazel run //cmd/gofuse -- --bazelout=darwin-fastbuild package main import ( @@ -54,7 +56,6 @@ var externals = map[string]string{ "org_golang_x_text": filepath.Join("golang.org", "x", "text"), "org_golang_x_tools": filepath.Join("golang.org", "x", "tools"), "org_golang_x_sys": filepath.Join("golang.org", "x", "sys"), - "llvm": "llvm", } var ( @@ -62,6 +63,8 @@ var ( bazelOutDirectory = flag.String("bazelout", "", "The bazel-out/X directory name from which to include .go files. E.g. k8-fastbuild, darwin-fastbuild, k8-dbg, etc.") + + runOnWindows = flag.Bool("force-run-on-windows", false, "Don't fail to run on Windows, even though it's unsupported.") ) func main() { @@ -74,6 +77,9 @@ func main() { } func run() error { + if runtime.GOOS == "windows" && !*runOnWindows { + return fmt.Errorf("cmd/gofuse is not supported on Windows") + } if len(*bazelOutDirectory) == 0 { switch runtime.GOOS { @@ -82,6 +88,8 @@ func run() error { break case "darwin": *bazelOutDirectory = "darwin-fastbuild" + case "darwin_arm64": + *bazelOutDirectory = "darwin_arm64-fastbuild" default: } @@ -173,14 +181,13 @@ func run() error { // Collect all the external package file mappings. - // After resolving symlinks, bazel-gapid/external points to: - // /home/paulthomson/.cache/bazel/_bazel_paulthomson/1234/execroot/gapid/external - // But this directory is not ideal, as it only includes the externals from the last bazel build or run. - // Instead, we can resolve bazel-gapid and go up to get to: + // After resolving symlinks, bazel-out points to: + // /home/paulthomson/.cache/bazel/_bazel_paulthomson/1234/execroot/gapid/bazel-out + // We edit the path to get: // /home/paulthomson/.cache/bazel/_bazel_paulthomson/1234/external // ...which includes all externals - // E.g. /home/paulthomson/.cache/bazel/_bazel_paulthomson/1234/execroot/gapid + // E.g. /home/paulthomson/.cache/bazel/_bazel_paulthomson/1234/execroot/gapid/bazel-out bazelGapidResolved, err := filepath.EvalSymlinks(filepath.Join(projectRoot, "bazel-out")) if err != nil { return err @@ -199,17 +206,26 @@ func run() error { extMapping := mappings{} for pkg, imp := range externals { src := filepath.Join(bazelExternals, pkg) - fmt.Println("Collecting .go files from:", src) + fmt.Println("Collecting .go, .h and .hpp files from:", src) dst := filepath.Join(fusedRoot, "src", imp) - m := collect(src, always).ifTrue(and(isFile, hasSuffix(".go"))). + m := collect(src, always).ifTrue(and(isFile, or(hasSuffix(".go"), hasSuffix(".h"), hasSuffix(".hpp")))). mapping(func(path string) string { return filepath.Join(dst, rel(src, path)) }) extMapping = append(extMapping, m...) } + thirdPartiesOut := filepath.Join(projectRoot, "bazel-out", *bazelOutDirectory, "bin", "tools", "build", "third_party") + fmt.Println("Collecting generated .go from:", thirdPartiesOut) + perfettoProtosMappingOut := collect(thirdPartiesOut, always).ifTrue(and( + isFile, + contains(filepath.Join("protos", "perfetto")), + hasSuffix(".go")), + ).mapping(func(path string) string { + return filepath.Join(fusedRoot, "src", trimUpTo(rel(projectRoot, path), "protos")) + }) // Every mapping we're going to deal with. - allMappings := join(srcMapping, genfilesMappingOut, binMappingOut, templateGenedGofiles, templateGenedCppfiles, extMapping) + allMappings := join(srcMapping, genfilesMappingOut, binMappingOut, templateGenedGofiles, templateGenedCppfiles, extMapping, perfettoProtosMappingOut) // Remove all existing symlinks in the fused directory that are not part of the // mappings. This may never happen if the OS automatically deletes deleted diff --git a/cmd/img2h/BUILD.bazel b/cmd/img2h/BUILD.bazel new file mode 100644 index 0000000000..69996a5134 --- /dev/null +++ b/cmd/img2h/BUILD.bazel @@ -0,0 +1,33 @@ +# Copyright (C) 2020 Google Inc. +# +# 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. + +load("//tools/build:rules.bzl", "go_stripped_binary") +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/google/gapid/cmd/img2h", + visibility = ["//visibility:private"], + deps = [ + "//core/app:go_default_library", + "//core/log:go_default_library", + ], +) + +go_stripped_binary( + name = "img2h", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/cmd/img2h/main.go b/cmd/img2h/main.go new file mode 100644 index 0000000000..8af7e5bee8 --- /dev/null +++ b/cmd/img2h/main.go @@ -0,0 +1,102 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +// img2h is a utility program that creates C++ headers containing image data. +// It is usefull to embed texture data within a binary to simplify loading. +package main + +import ( + "context" + "flag" + "fmt" + "image" + "image/color" + "os" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" + + // Import to register image formats. + _ "image/gif" + _ "image/jpeg" + _ "image/png" +) + +var ( + out = flag.String("out", "-", "Output file, '-' for stdout") +) + +func main() { + app.ShortHelp = "img2h generates C++ headers containing image data" + app.Name = "img2h" + app.Run(run) +} + +func run(ctx context.Context) error { + if flag.NArg() != 1 { + app.Usage(ctx, "Exactly one image file expected, got %d", flag.NArg()) + return nil + } + + in, err := os.Open(flag.Arg(0)) + if err != nil { + return log.Errf(ctx, err, "Failed to open %s", flag.Arg(0)) + } + defer in.Close() + + img, _, err := image.Decode(in) + if err != nil { + return log.Errf(ctx, err, "Failed to decode image") + } + + width, height := img.Bounds().Dx(), img.Bounds().Dy() + model := color.RGBAModel + + o := os.Stdout + if *out != "-" { + o, err = os.Create(*out) + if err != nil { + return log.Errf(ctx, err, "Failed to create %s", *out) + } + defer o.Close() + } + + fmt.Fprintf(o, "const struct {\n") + fmt.Fprintf(o, " VkFormat format;\n") + fmt.Fprintf(o, " size_t width;\n") + fmt.Fprintf(o, " size_t height;\n") + fmt.Fprintf(o, " struct {\n") + fmt.Fprintf(o, " uint8_t r;\n") + fmt.Fprintf(o, " uint8_t g;\n") + fmt.Fprintf(o, " uint8_t b;\n") + fmt.Fprintf(o, " uint8_t a;\n") + fmt.Fprintf(o, " } data[%d];\n", width*height) + fmt.Fprintf(o, "} texture = {\n") + fmt.Fprintf(o, " VK_FORMAT_R8G8B8A8_UNORM,\n") + fmt.Fprintf(o, " %d,\n", width) + fmt.Fprintf(o, " %d,\n", height) + fmt.Fprintf(o, " {\n") + + for y := 0; y < height; y++ { + for x := 0; x < width; x++ { + p := model.Convert(img.At(x, y)).(color.RGBA) + fmt.Fprintf(o, "{%d, %d, %d, %d}, ", p.R, p.G, p.B, 255) + } + fmt.Fprintf(o, "\n") + } + + fmt.Fprintf(o, " }\n") + fmt.Fprintf(o, "};\n") + return nil +} diff --git a/cmd/img2ico/BUILD.bazel b/cmd/img2ico/BUILD.bazel new file mode 100644 index 0000000000..16c9aa49c6 --- /dev/null +++ b/cmd/img2ico/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/google/gapid/cmd/img2ico", + visibility = ["//visibility:private"], + deps = [ + "//core/app:go_default_library", + "//core/log:go_default_library", + ], +) + +go_binary( + name = "img2ico", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/cmd/img2ico/main.go b/cmd/img2ico/main.go new file mode 100644 index 0000000000..075b2e3c25 --- /dev/null +++ b/cmd/img2ico/main.go @@ -0,0 +1,204 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +// img2ico is a utility program that converts images to Windows ICO icons. + +package main + +import ( + "bytes" + "context" + "encoding/binary" + "flag" + "image" + "image/color" + "os" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" + + // Import to register image formats. + _ "image/gif" + _ "image/jpeg" + "image/png" +) + +var ( + out = flag.String("out", "-", "Output file, '-' for stdout") +) + +func main() { + app.ShortHelp = "img2ico creates Windows ICO files" + app.Name = "img2ico" + app.Run(run) +} + +type header struct { + reserved uint16 + imgType uint16 + imgCount uint16 +} + +type entry struct { + width uint8 + height uint8 + paletteSize uint8 + reserved uint8 + numPlanes uint16 + bpp uint16 + imgSize uint32 + offset uint32 +} + +type bmp struct { + headerSize uint32 + width uint32 + height uint32 + planes uint16 + bpp uint16 + compression uint32 + imageSize uint32 + wResolution uint32 + hResolution uint32 + paletteSize uint32 + important uint32 +} + +type pixel struct { + b, g, r, a uint8 +} + +func run(ctx context.Context) (err error) { + if flag.NArg() < 1 { + app.Usage(ctx, "At least one input image is required") + return nil + } + + images := make([]image.Image, 0, flag.NArg()) + pngs := [][]byte{} + for _, file := range flag.Args() { + in, err := os.Open(file) + if err != nil { + return log.Errf(ctx, err, "Failed to open %s", file) + } + + img, _, err := image.Decode(in) + in.Close() + if err != nil { + return log.Errf(ctx, err, "Failed to decode image %s", file) + } + + size := img.Bounds().Size() + if size.X > 256 || size.Y > 256 { + return log.Errf(ctx, nil, "Image %s too big. Cannot be larger than 256x256", file) + } + + images = append(images, img) + + // Use PNG format for large icons. + if size.X == 256 || size.Y == 256 { + var pngOut bytes.Buffer + png.Encode(&pngOut, img) + pngs = append(pngs, pngOut.Bytes()) + } + } + + o := os.Stdout + if *out != "-" { + o, err = os.Create(*out) + if err != nil { + return log.Errf(ctx, err, "Failed to create %s", *out) + } + defer o.Close() + } + + binary.Write(o, binary.LittleEndian, &header{ + imgType: 1, // ICO + imgCount: uint16(len(images)), + }) + + offset := uint32(6 + 16*len(images)) + pngIdx := 0 + for _, img := range images { + w, h := img.Bounds().Dx(), img.Bounds().Dy() + size := uint32(40 + w*h*4 + h*((w+31) & ^31)/8) + + if w == 256 { + w = 0 + } + if h == 256 { + h = 0 + } + + if w == 0 || h == 0 { + size = uint32(len(pngs[pngIdx])) + pngIdx++ + } + + binary.Write(o, binary.LittleEndian, &entry{ + width: uint8(w), + height: uint8(h), + numPlanes: 1, + bpp: 32, + imgSize: size, + offset: offset, + }) + offset += size + } + + model := color.NRGBAModel + pngIdx = 0 + for _, img := range images { + w, h := img.Bounds().Dx(), img.Bounds().Dy() + + if w == 256 || h == 256 { + o.Write(pngs[pngIdx]) + pngIdx++ + continue + } + + binary.Write(o, binary.LittleEndian, &bmp{ + headerSize: 40, + width: uint32(w), + height: uint32(2 * h), + planes: 1, + bpp: 32, + imageSize: uint32(w * h * 4), + }) + + matte := make([]byte, h*((w+31) & ^31)/8) + mattePos := uint(0) + for y := h - 1; y >= 0; y-- { + for x := 0; x < w; x++ { + p := model.Convert(img.At(x, y)).(color.NRGBA) + binary.Write(o, binary.LittleEndian, &pixel{ + r: p.R, + g: p.G, + b: p.B, + a: p.A, + }) + if p.A == 0 { + matte[mattePos/8] |= byte(0x80 >> (mattePos % 8)) + } + mattePos++ + } + // Align to 4 bytes. + mattePos = (mattePos + 31) & ^uint(31) + } + + o.Write(matte) + } + + return nil +} diff --git a/cmd/launch_producer/BUILD.bazel b/cmd/launch_producer/BUILD.bazel index de53b56dec..b6881eda6d 100644 --- a/cmd/launch_producer/BUILD.bazel +++ b/cmd/launch_producer/BUILD.bazel @@ -15,7 +15,7 @@ load("//tools/build:rules.bzl", "android_native_binary") android_native_binary( - name = "launch_producer", + name = "agi_launch_producer", srcs = ["main.cpp"], linkopts = [ "-llog", diff --git a/cmd/launch_producer/main.cpp b/cmd/launch_producer/main.cpp index 0bd4f0f377..540e21f1b8 100644 --- a/cmd/launch_producer/main.cpp +++ b/cmd/launch_producer/main.cpp @@ -24,11 +24,10 @@ #include -// TODO(b/148950543): Figure out why stdout is not captured. -#define _LOG(lvl, name, msg, ...) \ - do { \ - fprintf(stderr, name ": " msg "\n", ##__VA_ARGS__); \ - __android_log_print(lvl, "GAPID", msg, ##__VA_ARGS__); \ +#define _LOG(lvl, name, msg, ...) \ + do { \ + fprintf(stderr, name ": " msg "\n", ##__VA_ARGS__); \ + __android_log_print(lvl, "AGI", msg, ##__VA_ARGS__); \ } while (false) #define LOG_ERR(msg, ...) _LOG(ANDROID_LOG_ERROR, "E", msg, ##__VA_ARGS__) @@ -42,7 +41,7 @@ typedef void (*FN_PTR)(void); const char* kProducerPaths[] = { "libgpudataproducer.so", }; -const char* kPidFileName = "/data/local/tmp/gapid_launch_producer.pid"; +const char* kPidFileName = "/data/local/tmp/agi_launch_producer.pid"; FN_PTR loadLibrary(const char* lib) { char* error; diff --git a/cmd/linearize_trace/BUILD.bazel b/cmd/linearize_trace/BUILD.bazel index 33da6f0e77..1823923c25 100644 --- a/cmd/linearize_trace/BUILD.bazel +++ b/cmd/linearize_trace/BUILD.bazel @@ -22,8 +22,6 @@ go_library( deps = [ "//core/app:go_default_library", "//core/log:go_default_library", - "//gapis/api/gles:go_default_library", - "//gapis/api/gvr:go_default_library", "//gapis/api/vulkan:go_default_library", "//gapis/capture:go_default_library", "//gapis/database:go_default_library", diff --git a/cmd/linearize_trace/main.go b/cmd/linearize_trace/main.go index 2940f5e660..25e94b1ef6 100644 --- a/cmd/linearize_trace/main.go +++ b/cmd/linearize_trace/main.go @@ -25,8 +25,6 @@ import ( "github.com/google/gapid/core/app" log "github.com/google/gapid/core/log" - _ "github.com/google/gapid/gapis/api/gles" - _ "github.com/google/gapid/gapis/api/gvr" _ "github.com/google/gapid/gapis/api/vulkan" "github.com/google/gapid/gapis/capture" "github.com/google/gapid/gapis/database" diff --git a/cmd/lingo/BUILD.bazel b/cmd/lingo/BUILD.bazel deleted file mode 100644 index 5938680ee8..0000000000 --- a/cmd/lingo/BUILD.bazel +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "github.com/google/gapid/cmd/lingo", - visibility = ["//visibility:private"], - deps = [ - "//core/app:go_default_library", - "//test/robot/lingo/generator:go_default_library", - ], -) - -go_binary( - name = "lingo", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/cmd/lingo/main.go b/cmd/lingo/main.go deleted file mode 100644 index a29c72c186..0000000000 --- a/cmd/lingo/main.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// The syntax command is used to rewrite recursive descent parser syntax declaration files. -package main - -import ( - "context" - "flag" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/test/robot/lingo/generator" -) - -var ( - base string -) - -func main() { - app.ShortHelp = "syntax: A recursive descent parser generator." - flag.StringVar(&base, "base", base, "don't update the copyright if it's just old") - app.Run(run) -} - -func run(ctx context.Context) error { - args := flag.Args() - if len(args) < 1 { - app.Usage(ctx, "Expect at least one lingo file") - return nil - } - return generator.RewriteFiles(ctx, base, args...) -} diff --git a/cmd/llvm-build/BUILD.bazel b/cmd/llvm-build/BUILD.bazel deleted file mode 100644 index 4fbec83808..0000000000 --- a/cmd/llvm-build/BUILD.bazel +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "github.com/google/gapid/cmd/llvm-build", - visibility = ["//visibility:private"], -) - -go_binary( - name = "llvm-build", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/cmd/llvm-build/main.go b/cmd/llvm-build/main.go deleted file mode 100644 index db64826c8b..0000000000 --- a/cmd/llvm-build/main.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// Command llvm-build generates bazel build rules for the LLVM dependency. -package main - -import ( - "bufio" - "fmt" - "os" - "path/filepath" - "sort" - "strings" -) - -type entry struct { - name string - mode string - path string - deps []string -} - -func main() { - base := os.Args[1] - libs := []entry{} - err := filepath.Walk(base, func(path string, info os.FileInfo, err error) error { - if info.Name() != "LLVMBuild.txt" { - return nil - } - dir := filepath.Dir(path) - rel, err := filepath.Rel(base, dir) - if err != nil { - return err - } - file, err := os.Open(path) - if err != nil { - return err - } - scanner := bufio.NewScanner(file) - lib := entry{path: rel} - key := "" - for scanner.Scan() { - line := strings.TrimSpace(scanner.Text()) - if len(line) == 0 { - continue - } - if line[0] == ';' { - continue - } - if line[0] == '[' { - libs = append(libs, lib) - lib = entry{path: rel} - continue - } - if words := strings.SplitN(line, "=", 2); len(words) == 2 { - key = strings.TrimSpace(words[0]) - line = strings.TrimSpace(words[1]) - } - if line == "" { - continue - } - switch key { - case "name": - lib.name += line - case "type": - lib.mode += line - case "required_libraries": - lib.deps = append(lib.deps, strings.Split(line, " ")...) - } - key = "" - } - libs = append(libs, lib) - return nil - }) - if err != nil { - panic(err) - } - fmt.Printf(`# AUTOGENERATED FILE -# This file is automatically generated from the LLVMBuild.txt files -# Do not change this file by hand. -# See cmd/llvm-build/main.go for details. -# To update this file run -# bazel run //cmd/llvm-build $(bazel info output_base)/external/llvm > $(bazel info workspace)/tools/build/third_party/llvm/libs.bzl - -load("@gapid//tools/build/third_party:llvm/rules.bzl", "llvmLibrary") - -def llvm_auto_libs(**kwargs): -`) - for _, lib := range libs { - if lib.name == "" || lib.mode != "Library" { - continue - } - fmt.Printf(" llvm%v(\n name=\"%v\",\n path=\"%v\",\n deps=[", lib.mode, lib.name, lib.path) - sort.Strings(lib.deps) - for i, dep := range lib.deps { - if i > 0 { - fmt.Printf(`, `) - } - fmt.Printf(`":%v"`, dep) - } - fmt.Printf("],\n **kwargs\n )\n") - } -} diff --git a/cmd/perfetto/trace.go b/cmd/perfetto/trace.go index 7b84653d1a..1610610434 100644 --- a/cmd/perfetto/trace.go +++ b/cmd/perfetto/trace.go @@ -20,26 +20,31 @@ import ( "errors" "flag" "fmt" + "io" "io/ioutil" "os" + "time" "github.com/golang/protobuf/proto" "github.com/google/gapid/core/app" "github.com/google/gapid/core/app/crash" "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/device/bind" + "github.com/google/gapid/gapis/perfetto" config "protos/perfetto/config" ) type traceVerb struct { - Config string `help:"File containing the trace configuration proto."` - Out string `help:"The file to store the trace data in."` + Config string `help:"File containing the trace configuration proto."` + Out string `help:"The file to store the trace data in."` + Read time.Duration `help:"The time to wait in-between read requests."` } func init() { verb := &traceVerb{ - Out: "trace.perfetto", + Out: "trace.perfetto", + Read: 100 * time.Millisecond, } app.AddVerb(&app.Verb{ Name: "trace", @@ -77,7 +82,7 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { } defer c.Close(ctx) - sess, err := c.Trace(ctx, cfg, out) + sess, err := c.Trace(ctx, cfg, &trackingWriter{out, 0, time.Now()}) if err != nil { return log.Errf(ctx, err, "Failed to start Perfetto trace") } @@ -92,6 +97,20 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { sess.Start(ctx) } + crash.Go(func() { + ticker := time.NewTicker(verb.Read) + for { + <-ticker.C + if err := sess.Read(ctx); err != nil { + if err != perfetto.ErrDone { + fmt.Println("Reading failed:", err) + } + ticker.Stop() + break + } + } + }) + fmt.Println("Press enter to stop capturing...") if _, err := reader.ReadString('\n'); err != nil { return @@ -106,6 +125,23 @@ func (verb *traceVerb) Run(ctx context.Context, flags flag.FlagSet) error { return nil } +type trackingWriter struct { + out io.Writer + done int64 + last time.Time +} + +// Write implements the io.Writer interface. +func (w *trackingWriter) Write(p []byte) (int, error) { + n, err := w.out.Write(p) + w.done += int64(n) + if time.Since(w.last) >= 1*time.Second { + fmt.Println("Got", w.done, "bytes...") + w.last = time.Now() + } + return n, err +} + func (verb *traceVerb) readConfig(ctx context.Context) (*config.TraceConfig, error) { data, err := ioutil.ReadFile(verb.Config) if err != nil { diff --git a/cmd/regres/main.go b/cmd/regres/main.go index afc94e15b8..71928fe0b5 100644 --- a/cmd/regres/main.go +++ b/cmd/regres/main.go @@ -193,7 +193,7 @@ func run(ctx context.Context) error { // Gather incremental build stats if *incBuild { - if err := withTouchedGLES(ctx, rnd, func() error { + if err := withTouchedVulkan(ctx, rnd, func() error { log.I(ctx, "HEAD~%.2d: Building incremental change at %v: %v", i, sha, cl.Subject) if duration, err := build(ctx); err == nil { r.IncrementalBuildTime = duration.Seconds() @@ -257,19 +257,19 @@ func run(ctx context.Context) error { return nil } -func withTouchedGLES(ctx context.Context, r *rand.Rand, f func() error) error { - glesAPIPath := filepath.Join(*root, "gapis", "api", "gles", "gles.api") - fi, err := os.Stat(glesAPIPath) +func withTouchedVulkan(ctx context.Context, r *rand.Rand, f func() error) error { + vulkanAPIPath := filepath.Join(*root, "gapis", "api", "vulkan", "vulkan.api") + fi, err := os.Stat(vulkanAPIPath) if err != nil { return err } - glesAPI, err := ioutil.ReadFile(glesAPIPath) + vulkanAPI, err := ioutil.ReadFile(vulkanAPIPath) if err != nil { return err } - modGlesAPI := []byte(fmt.Sprintf("%v\ncmd void fake_cmd_%d() {}\n", string(glesAPI), r.Int())) - ioutil.WriteFile(glesAPIPath, modGlesAPI, fi.Mode().Perm()) - defer ioutil.WriteFile(glesAPIPath, glesAPI, fi.Mode().Perm()) + modVulkanAPI := []byte(fmt.Sprintf("%v\ncmd void fake_cmd_%d() {}\n", string(vulkanAPI), r.Int())) + ioutil.WriteFile(vulkanAPIPath, modVulkanAPI, fi.Mode().Perm()) + defer ioutil.WriteFile(vulkanAPIPath, vulkanAPI, fi.Mode().Perm()) return f() } @@ -298,6 +298,8 @@ func dllExt(n string) string { return n + ".dll" case "darwin": return n + ".dylib" + case "darwin_arm64": + return n + ".dylib" default: return n + ".so" } diff --git a/cmd/robot/BUILD.bazel b/cmd/robot/BUILD.bazel deleted file mode 100644 index 10362b322c..0000000000 --- a/cmd/robot/BUILD.bazel +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "actions.go", - "build.go", - "job.go", - "main.go", - "master.go", - "replay.go", - "report.go", - "stash.go", - "subject.go", - "trace.go", - "upload.go", - "web.go", - ], - importpath = "github.com/google/gapid/cmd/robot", - visibility = ["//visibility:private"], - deps = [ - "//core/app:go_default_library", - "//core/app/crash:go_default_library", - "//core/git:go_default_library", - "//core/log:go_default_library", - "//core/net/grpcutil:go_default_library", - "//core/os/device:go_default_library", - "//core/os/device/host:go_default_library", - "//core/os/file:go_default_library", - "//core/os/flock:go_default_library", - "//test/robot/build:go_default_library", - "//test/robot/job:go_default_library", - "//test/robot/master:go_default_library", - "//test/robot/monitor:go_default_library", - "//test/robot/record:go_default_library", - "//test/robot/replay:go_default_library", - "//test/robot/report:go_default_library", - "//test/robot/scheduler:go_default_library", - "//test/robot/search/script:go_default_library", - "//test/robot/stash:go_default_library", - "//test/robot/stash/grpc:go_default_library", - "//test/robot/stash/local:go_default_library", - "//test/robot/subject:go_default_library", - "//test/robot/trace:go_default_library", - "//test/robot/web:go_default_library", - "@com_github_golang_protobuf//proto:go_default_library", - "@com_github_golang_protobuf//ptypes:go_default_library_gen", - "@org_golang_google_grpc//:go_default_library", - ], -) - -go_binary( - name = "robot", - data = select({ - "//tools/build:no-android": [], - "//conditions:default": [ - "//gapidapk/android/apk:arm64-v8a.apk", - "//gapidapk/android/apk:armeabi-v7a.apk", - "//gapidapk/android/apk:x86.apk", - ], - }), - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/cmd/robot/actions.go b/cmd/robot/actions.go deleted file mode 100644 index b285c3aadb..0000000000 --- a/cmd/robot/actions.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/master" - "google.golang.org/grpc" -) - -type RobotOptions struct { - ServerAddress string `help:"The master server address"` -} - -var ( - startVerb = app.AddVerb(&app.Verb{ - Name: "start", - ShortHelp: "Start a server", - }) - searchVerb = app.AddVerb(&app.Verb{ - Name: "search", - ShortHelp: "Search for content in the server", - }) - uploadVerb = app.AddVerb(&app.Verb{ - Name: "upload", - ShortHelp: "Upload a file to a server", - }) - setVerb = app.AddVerb(&app.Verb{ - Name: "set", - ShortHelp: "Sets a value in a server", - }) - defaultRobotOptions = RobotOptions{ServerAddress: defaultMasterAddress} -) - -func init() { - app.AddVerb(&app.Verb{ - Name: "stop", - ShortHelp: "Stop a server", - Action: &stopVerb{RobotOptions: defaultRobotOptions}, - }) - app.AddVerb(&app.Verb{ - Name: "restart", - ShortHelp: "Restart a server", - Action: &restartVerb{RobotOptions: defaultRobotOptions}, - }) -} - -type stopVerb struct { - RobotOptions - - Now bool `help:"Immediate shutdown"` -} - -func (v *stopVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - client := master.NewClient(ctx, master.NewRemoteMaster(ctx, conn)) - if v.Now { - return client.Kill(ctx, flags.Args()...) - } - return client.Shutdown(ctx, flags.Args()...) - }, grpc.WithInsecure()) -} - -type restartVerb struct { - RobotOptions -} - -func (v *restartVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - client := master.NewClient(ctx, master.NewRemoteMaster(ctx, conn)) - return client.Restart(ctx, flags.Args()...) - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/build.go b/cmd/robot/build.go deleted file mode 100644 index a941b10199..0000000000 --- a/cmd/robot/build.go +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "bytes" - "context" - "errors" - "flag" - "os" - "os/user" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/git" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/core/os/device" - "github.com/google/gapid/core/os/device/host" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/test/robot/build" - "github.com/google/gapid/test/robot/search/script" - "google.golang.org/grpc" -) - -type UploadOptions struct { - RobotOptions - CL string `help:"The build CL, will be guessed if not set"` - Description string `help:"An optional build description"` - Tag string `help:"The optional build tag"` - Track string `help:"The package's track, will be guessed if not set"` - Uploader string `help:"The uploading entity, will be guessed if not set"` - BuilderAbi string `help:"The abi of the builder device, will assume this device if not set"` - BuildBot bool `help:"Whether this package was built by a build bot"` -} - -func init() { - uploadVerb.Add(&app.Verb{ - Name: "build", - ShortHelp: "Upload a build to the server", - ShortUsage: "", - Action: &buildUploadVerb{UploadOptions: UploadOptions{RobotOptions: defaultRobotOptions}}, - }) - searchVerb.Add(&app.Verb{ - Name: "artifact", - ShortHelp: "List build artifacts in the server", - ShortUsage: "", - Action: &artifactSearchVerb{RobotOptions: defaultRobotOptions}, - }) - searchVerb.Add(&app.Verb{ - Name: "package", - ShortHelp: "List build packages in the server", - ShortUsage: "", - Action: &packageSearchVerb{RobotOptions: defaultRobotOptions}, - }) - searchVerb.Add(&app.Verb{ - Name: "track", - ShortHelp: "List build tracks in the server", - ShortUsage: "", - Action: &trackSearchVerb{RobotOptions: defaultRobotOptions}, - }) - setVerb.Add(&app.Verb{ - Name: "track", - ShortHelp: "Sets values on a track", - ShortUsage: "", - Action: &trackUpdateVerb{RobotOptions: defaultRobotOptions}, - }) - setVerb.Add(&app.Verb{ - Name: "package", - ShortHelp: "Sets values on a package", - ShortUsage: "", - Action: &packageUpdateVerb{RobotOptions: defaultRobotOptions}, - }) -} - -type buildUploadVerb struct { - UploadOptions - - store build.Store - info *build.Information -} - -func (v *buildUploadVerb) Run(ctx context.Context, flags flag.FlagSet) (err error) { - if flags.NArg() != 1 { - err = errors.New("Missing expected argument") - return log.Err(ctx, err, "build upload expects a single filepath as argument") - } - - p := file.Abs(flags.Arg(0)) - u := make([]uploadable, 1) - if p.IsDir() { - b, err := zip(p) - if err != nil { - return log.Err(ctx, err, "failed to create artifact zip") - } - u[0] = data(b.Bytes(), p.Basename()+".zip", false) - } else { - u[0] = path(p.System()) - } - - return upload(ctx, u, v.ServerAddress, v) -} - -func (v *buildUploadVerb) prepare(ctx context.Context, conn *grpc.ClientConn) (err error) { - v.store = build.NewRemote(ctx, conn) - v.info, err = v.UploadOptions.createBuildInfo(ctx) - return -} - -func (v *buildUploadVerb) process(ctx context.Context, id string) error { - return storeBuild(ctx, v.store, v.info, id) -} - -func storeBuild(ctx context.Context, store build.Store, info *build.Information, id string) error { - id, merged, err := store.Add(ctx, id, info) - if err != nil { - return log.Err(ctx, err, "Failed processing build") - } - if merged { - log.I(ctx, "Merged with build set %s", id) - } else { - log.I(ctx, "New build set %s", id) - } - return nil -} - -func (o *UploadOptions) createBuildInfo(ctx context.Context) (*build.Information, error) { - // see if we can find a git cl in the cwd - var typ build.Type - if o.BuildBot { - typ = build.BuildBot - if o.CL == "" || o.Description == "" || o.BuilderAbi == "" { - err := errors.New("Missing expected argument") - return nil, log.Err(ctx, err, "build bot packages require the CL, desciption and builder ABI") - } - if o.Track == "" { - o.Track = "master" - } - } else { - typ = o.initFromGit(ctx) - } - - if o.Uploader == "" { - // guess uploader from environment - if user, err := user.Current(); err == nil { - o.Uploader = user.Username - log.I(ctx, "Detected uploader %s", o.Uploader) - } - } - log.I(ctx, "Detected build type %s", typ) - builder := &device.Instance{Configuration: &device.Configuration{}} - if o.BuilderAbi != "" { - if typ == build.BuildBot { - builder.Name = "BuildBot" - } - builder.Configuration.ABIs = []*device.ABI{device.ABIByName(o.BuilderAbi)} - } - if len(builder.Configuration.ABIs) == 0 { - builder = host.Instance(ctx) - } - return &build.Information{ - Type: typ, - Branch: o.Track, - Cl: o.CL, - Tag: o.Tag, - Description: o.Description, - Builder: builder, - Uploader: o.Uploader, - }, nil -} - -func (o *UploadOptions) initFromGit(ctx context.Context) (typ build.Type) { - // Assume not clean, until we can verify. - typ = build.Local - - g, err := git.New(".") - if err != nil { - log.E(ctx, "Git failed. Error: %v", err) - return - } - - if cl, err := g.HeadCL(ctx, o.CL); err != nil { - log.E(ctx, "CL failed. Error: %v", err) - } else { - if o.CL == "" { - // guess cl from git - o.CL = cl.SHA.String() - log.I(ctx, "Detected CL %s", o.CL) - } - if o.Description == "" { - // guess description from git - o.Description = cl.Subject - log.I(ctx, "Detected description %s", o.Description) - } - } - - if status, err := g.Status(ctx); err != nil { - log.E(ctx, "Status failed. Error: %v", err) - } else if status.Clean() { - typ = build.User - } - - if o.Track == "" { - // guess branch from git - if branch, err := g.CurrentBranch(ctx); err != nil { - log.E(ctx, "Branch failed. Error: %v", err) - o.Track = "auto" - } else { - o.Track = branch - log.I(ctx, "Detected track %s", o.Track) - } - } - return -} - -func zip(in file.Path) (*bytes.Buffer, error) { - buf := new(bytes.Buffer) - if err := file.ZIP(buf, in); err != nil { - return nil, err - } - return buf, nil -} - -type artifactSearchVerb struct { - RobotOptions -} - -func (v *artifactSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - b := build.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return b.SearchArtifacts(ctx, expr.Query(), func(ctx context.Context, entry *build.Artifact) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} - -type packageSearchVerb struct { - RobotOptions -} - -func (v *packageSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - b := build.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return b.SearchPackages(ctx, expr.Query(), func(ctx context.Context, entry *build.Package) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} - -type trackSearchVerb struct { - RobotOptions -} - -func (v *trackSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - b := build.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return b.SearchTracks(ctx, expr.Query(), func(ctx context.Context, entry *build.Track) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} - -var idOrName = script.MustParse("Id == $ or Name == $").Using("$") - -type trackUpdateVerb struct { - RobotOptions - Name string `help:"The new name for the track"` - Description string `help:"A description of the track"` - Pkg string `help:"The id of the package at the head of the track"` -} - -func (v *trackUpdateVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - b := build.NewRemote(ctx, conn) - args := flags.Args() - track := &build.Track{ - Name: v.Name, - Description: v.Description, - Head: v.Pkg, - } - if len(args) != 0 { - // Updating an existing track, find it first - err := b.SearchTracks(ctx, idOrName(args[0]).Query(), func(ctx context.Context, entry *build.Track) error { - if track.Id != "" { - return log.Err(ctx, nil, "Multiple tracks matched") - } - track.Id = entry.Id - return nil - }) - if err != nil { - return err - } - if track.Id == "" { - return log.Err(ctx, nil, "No tracks matched") - } - } - track, err := b.UpdateTrack(ctx, track) - if err != nil { - return err - } - log.I(ctx, track.String()) - return nil - }, grpc.WithInsecure()) -} - -type packageUpdateVerb struct { - RobotOptions - Description string `help:"A description of the track"` - Parent string `help:"The id of the package that will be the new parent"` -} - -func (v *packageUpdateVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - b := build.NewRemote(ctx, conn) - args := flags.Args() - pkg := &build.Package{ - Information: &build.Information{Description: v.Description}, - Parent: v.Parent, - } - if len(args) == 0 { - return log.Err(ctx, nil, "Missing argument, must specify a package to update") - } - err := b.SearchPackages(ctx, script.MustParse("Id == $").Using("$")(args[0]).Query(), func(ctx context.Context, entry *build.Package) error { - if pkg.Id != "" { - return log.Err(ctx, nil, "Multiple packages matched") - } - pkg.Id = entry.Id - return nil - }) - if err != nil { - return err - } - if pkg.Id == "" { - return log.Err(ctx, nil, "No packages matched") - } - pkg, err = b.UpdatePackage(ctx, pkg) - if err != nil { - return err - } - log.I(ctx, pkg.String()) - return nil - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/job.go b/cmd/robot/job.go deleted file mode 100644 index 7e90ebbd70..0000000000 --- a/cmd/robot/job.go +++ /dev/null @@ -1,140 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "io/ioutil" - "os" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/app/crash" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/test/robot/job" - "github.com/google/gapid/test/robot/master" - "github.com/google/gapid/test/robot/monitor" - "github.com/google/gapid/test/robot/replay" - "github.com/google/gapid/test/robot/report" - "github.com/google/gapid/test/robot/search/script" - stashgrpc "github.com/google/gapid/test/robot/stash/grpc" - "github.com/google/gapid/test/robot/trace" - "google.golang.org/grpc" -) - -func init() { - searchVerb.Add(&app.Verb{ - Name: "device", - ShortHelp: "List the devices", - ShortUsage: "", - Action: &deviceSearchFlags{RobotOptions: defaultRobotOptions}, - }) - searchVerb.Add(&app.Verb{ - Name: "worker", - ShortHelp: "List the workers", - ShortUsage: "", - Action: &workerSearchFlags{RobotOptions: defaultRobotOptions}, - }) - startVerb.Add(&app.Verb{ - Name: "worker", - ShortHelp: "Starts a robot worker", - Action: &workerStartFlags{RobotOptions: defaultRobotOptions}, - }) -} - -type deviceSearchFlags struct { - RobotOptions -} - -func (v *deviceSearchFlags) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - w := job.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return w.SearchDevices(ctx, expr.Query(), func(ctx context.Context, entry *job.Device) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} - -type workerSearchFlags struct { - RobotOptions -} - -func (v *workerSearchFlags) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - w := job.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return w.SearchWorkers(ctx, expr.Query(), func(ctx context.Context, entry *job.Worker) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} - -type workerStartFlags struct { - RobotOptions -} - -func (v *workerStartFlags) Run(ctx context.Context, flags flag.FlagSet) error { - tempName, err := ioutil.TempDir("", "robot") - if err != nil { - return err - } - tempDir := file.Abs(tempName) - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - m := master.NewClient(ctx, master.NewRemoteMaster(ctx, conn)) - managers := monitor.Managers{ - Stash: stashgrpc.MustConnect(ctx, conn), - Trace: trace.NewRemote(ctx, conn), - Report: report.NewRemote(ctx, conn), - Replay: replay.NewRemote(ctx, conn), - } - if err := startAllWorkers(ctx, managers, tempDir); err != nil { - return err - } - shutdown, err := m.Orbit(ctx, master.ServiceList{Worker: true}) - if err != nil { - return err - } - if shutdown.Restart { - return app.Restart - } - return nil - }, grpc.WithInsecure()) -} - -func startAllWorkers(ctx context.Context, managers monitor.Managers, tempDir file.Path) error { - ctx = job.BindRegistry(ctx) - // TODO: not just ignore all the errors... - crash.Go(func() { trace.Run(ctx, managers.Stash, managers.Trace, tempDir) }) - crash.Go(func() { report.Run(ctx, managers.Stash, managers.Report, tempDir) }) - crash.Go(func() { replay.Run(ctx, managers.Stash, managers.Replay, tempDir) }) - return nil -} diff --git a/cmd/robot/main.go b/cmd/robot/main.go deleted file mode 100644 index 467ee2a6e4..0000000000 --- a/cmd/robot/main.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "github.com/google/gapid/core/app" - _ "github.com/google/gapid/test/robot/stash/grpc" - _ "github.com/google/gapid/test/robot/stash/local" -) - -func main() { - app.ShortHelp = "Robot is a command line tool for interacting with gapid automatic test servers." - app.Run(app.VerbMain) -} diff --git a/cmd/robot/master.go b/cmd/robot/master.go deleted file mode 100644 index 652f32fb73..0000000000 --- a/cmd/robot/master.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "net" - "net/url" - "strings" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/app/crash" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/core/os/flock" - "github.com/google/gapid/test/robot/build" - "github.com/google/gapid/test/robot/job" - "github.com/google/gapid/test/robot/master" - "github.com/google/gapid/test/robot/monitor" - "github.com/google/gapid/test/robot/record" - "github.com/google/gapid/test/robot/replay" - "github.com/google/gapid/test/robot/report" - "github.com/google/gapid/test/robot/scheduler" - "github.com/google/gapid/test/robot/search/script" - "github.com/google/gapid/test/robot/stash" - stashgrpc "github.com/google/gapid/test/robot/stash/grpc" - "github.com/google/gapid/test/robot/subject" - "github.com/google/gapid/test/robot/trace" - "github.com/google/gapid/test/robot/web" - "google.golang.org/grpc" -) - -const defaultMasterPort = 8081 - -var defaultMasterAddress = fmt.Sprintf("localhost:%v", defaultMasterPort) - -func init() { - startVerb.Add(&app.Verb{ - Name: "master", - ShortHelp: "Starts a robot master server", - Action: &masterVerb{ - BaseAddr: file.Abs("."), - StashAddr: "", - ShelfAddr: "", - Port: defaultMasterPort, - StartWorkers: true, - StartWeb: true, - WebPort: 8080, - }, - }) - searchVerb.Add(&app.Verb{ - Name: "master", - ShortHelp: "List satellites registered with the master", - ShortUsage: "", - Action: &masterSearchVerb{RobotOptions: defaultRobotOptions}, - }) -} - -type masterVerb struct { - BaseAddr file.Path `help:"The base path for all robot files"` - StashAddr string `help:"The address of the stash, defaults to a directory below base"` - ShelfAddr string `help:"The path to the persisted data, defaults to a directory below base"` - Port int `help:"The port to serve the master on"` - StartWorkers bool `help:"Enables local workers"` - StartWeb bool `help:"Enables serving the web client"` - WebPort int `help:"The port to serve the website on"` - Root file.Path `help:"The directory to use as the root of static content"` -} - -func (v *masterVerb) Run(ctx context.Context, flags flag.FlagSet) error { - tempName, err := ioutil.TempDir("", "robot") - if err != nil { - return err - } - tempDir := file.Abs(tempName) - restart := false - serverAddress := fmt.Sprintf(":%v", v.Port) - err = grpcutil.Serve(ctx, serverAddress, func(ctx context.Context, listener net.Listener, server *grpc.Server) error { - managers := monitor.Managers{} - err := error(nil) - var stashURL *url.URL - var shelfURL *url.URL - if v.StashAddr == "" { - stashURL = v.BaseAddr.Join("stash").URL() - } else if shelfURL, err = url.Parse(v.StashAddr); err != nil { - return log.Errf(ctx, err, "Invalid server location", v.StashAddr) - } - if v.ShelfAddr == "" { - shelfURL = v.BaseAddr.Join("shelf").URL() - } else if stashURL, err = url.Parse(v.ShelfAddr); err != nil { - return log.Errf(ctx, err, "Invalid record shelf location", v.ShelfAddr) - } - library := record.NewLibrary(ctx) - shelf, err := record.NewShelf(ctx, shelfURL) - if err != nil { - return log.Errf(ctx, err, "Could not open shelf: %v", shelfURL) - } - library.Add(ctx, shelf) - if managers.Stash, err = stash.Dial(ctx, stashURL); err != nil { - return log.Errf(ctx, err, "Could not open stash: %v", stashURL) - } - managers.Master = master.NewLocal(ctx) - if managers.Subject, err = subject.NewLocal(ctx, library, managers.Stash); err != nil { - return err - } - if managers.Build, err = build.NewLocal(ctx, managers.Stash, library); err != nil { - return err - } - if managers.Job, err = job.NewLocal(ctx, library); err != nil { - return err - } - if managers.Trace, err = trace.NewLocal(ctx, library, managers.Job); err != nil { - return err - } - if managers.Report, err = report.NewLocal(ctx, library, managers.Job); err != nil { - return err - } - if managers.Replay, err = replay.NewLocal(ctx, library, managers.Job); err != nil { - return err - } - if err := serveAll(ctx, server, managers); err != nil { - return err - } - if err := flock.ReleaseAllLocks(); err != nil { - return log.Errf(ctx, err, "Could not remove FLock files for all devices") - } - if v.StartWorkers { - if err := startAllWorkers(ctx, managers, tempDir); err != nil { - return err - } - } - crash.Go(func() { - if err := monitor.Run(ctx, managers, monitor.NewDataOwner(), scheduler.Tick); err != nil { - log.E(ctx, "Scheduler died. Error: %v", err) - } - }) - - if v.StartWeb { - config := web.Config{ - Port: v.WebPort, - StaticRoot: v.Root, - Managers: managers, - } - w, err := web.Create(ctx, config) - if err != nil { - return err - } - crash.Go(func() { w.Serve(ctx) }) - } - - c := master.NewClient(ctx, managers.Master) - services := master.ServiceList{ - Master: true, - Worker: v.StartWorkers, - Web: v.StartWeb, - } - crash.Go(func() { - shutdown, err := c.Orbit(ctx, services) - if err != nil { - log.I(ctx, "Orbit failed") - server.Stop() - return - } - restart = shutdown.Restart - if shutdown.Now { - log.I(ctx, "Kill now") - server.Stop() - } else { - log.I(ctx, "Graceful stop") - server.GracefulStop() - } - }) - return nil - }) - if restart { - return app.Restart - } - return err -} - -func serveAll(ctx context.Context, server *grpc.Server, managers monitor.Managers) error { - if err := master.Serve(ctx, server, managers.Master); err != nil { - return err - } - if err := stashgrpc.Serve(ctx, server, managers.Stash); err != nil { - return err - } - if err := subject.Serve(ctx, server, managers.Subject); err != nil { - return err - } - if err := build.Serve(ctx, server, managers.Build); err != nil { - return err - } - if err := job.Serve(ctx, server, managers.Job); err != nil { - return err - } - if err := trace.Serve(ctx, server, managers.Trace); err != nil { - return err - } - if err := report.Serve(ctx, server, managers.Report); err != nil { - return err - } - if err := replay.Serve(ctx, server, managers.Replay); err != nil { - return err - } - return nil -} - -type masterSearchVerb struct { - RobotOptions -} - -func (v *masterSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - m := master.NewRemoteMaster(ctx, conn) - expression := strings.Join(flags.Args(), " ") - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return m.Search(ctx, expr.Query(), func(ctx context.Context, entry *master.Satellite) error { - log.I(ctx, "%s", entry.String()) - return nil - }) - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/replay.go b/cmd/robot/replay.go deleted file mode 100644 index e4c36018a6..0000000000 --- a/cmd/robot/replay.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "os" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/replay" - "github.com/google/gapid/test/robot/search/script" - "google.golang.org/grpc" -) - -func init() { - searchVerb.Add(&app.Verb{ - Name: "replay", - ShortHelp: "List build replays in the server", - ShortUsage: "", - Action: &replaySearchFlags{RobotOptions: defaultRobotOptions}, - }) -} - -type replaySearchFlags struct { - RobotOptions -} - -func (v *replaySearchFlags) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - replays := replay.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return replays.Search(ctx, expr.Query(), func(ctx context.Context, entry *replay.Action) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/report.go b/cmd/robot/report.go deleted file mode 100644 index 83d6183d1f..0000000000 --- a/cmd/robot/report.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "os" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/report" - "github.com/google/gapid/test/robot/search/script" - "google.golang.org/grpc" -) - -func init() { - searchVerb.Add(&app.Verb{ - Name: "report", - ShortHelp: "List build reports in the server", - ShortUsage: "", - Action: &reportSearchFlags{RobotOptions: defaultRobotOptions}, - }) -} - -type reportSearchFlags struct { - RobotOptions -} - -func (v *reportSearchFlags) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - reports := report.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return reports.Search(ctx, expr.Query(), func(ctx context.Context, entry *report.Action) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/stash.go b/cmd/robot/stash.go deleted file mode 100644 index aca6bb63dd..0000000000 --- a/cmd/robot/stash.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "os" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/search/script" - "github.com/google/gapid/test/robot/stash" - stashgrpc "github.com/google/gapid/test/robot/stash/grpc" - "google.golang.org/grpc" -) - -func init() { - uploadVerb.Add(&app.Verb{ - Name: "stash", - ShortHelp: "Upload a file to the stash", - ShortUsage: "", - Action: &stashUploadVerb{RobotOptions: defaultRobotOptions}, - }) - searchVerb.Add(&app.Verb{ - Name: "stash", - ShortHelp: "List entries in the stash", - ShortUsage: "", - Action: &stashSearchVerb{RobotOptions: defaultRobotOptions}, - }) -} - -type stashUploadVerb struct { - RobotOptions -} - -func (v *stashUploadVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return upload(ctx, paths(flags.Args()), v.ServerAddress, v) -} -func (stashUploadVerb) prepare(context.Context, *grpc.ClientConn) error { return nil } -func (stashUploadVerb) process(context.Context, string) error { return nil } - -type stashSearchVerb struct { - RobotOptions -} - -func (v *stashSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - store, err := stashgrpc.Connect(ctx, conn) - if err != nil { - return err - } - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return store.Search(ctx, expr.Query(), func(ctx context.Context, entry *stash.Entity) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/subject.go b/cmd/robot/subject.go deleted file mode 100644 index 0d25fe5236..0000000000 --- a/cmd/robot/subject.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "os" - "strings" - "time" - - "github.com/google/gapid/core/os/file" - - "github.com/golang/protobuf/proto" - "github.com/golang/protobuf/ptypes" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/search/script" - "github.com/google/gapid/test/robot/subject" - "google.golang.org/grpc" -) - -func init() { - uploadVerb.Add(&app.Verb{ - Name: "subject", - ShortHelp: "Upload a traceable application to the server", - ShortUsage: "", - Action: &subjectUploadVerb{RobotOptions: defaultRobotOptions, API: GLESAPI, ObserveFrames: 0}, - }) - searchVerb.Add(&app.Verb{ - Name: "subject", - ShortHelp: "List traceable applications in the server", - ShortUsage: "", - Action: &subjectSearchVerb{RobotOptions: defaultRobotOptions}, - }) - setVerb.Add(&app.Verb{ - Name: "subject", - ShortHelp: "Sets values on a subject", - ShortUsage: "", - Action: &subjectUpdateVerb{RobotOptions: defaultRobotOptions}, - }) -} - -const ( - GLESAPI APIType = iota - VulkanAPI -) - -type APIType uint8 - -var apiTypeToName = map[APIType]string{ - GLESAPI: "gles", - VulkanAPI: "vulkan", -} - -func (a *APIType) Choose(c interface{}) { - *a = c.(APIType) -} -func (a APIType) String() string { - return apiTypeToName[a] -} - -type subjectUploadVerb struct { - RobotOptions - API APIType `help:"the api to capture, can be either gles or vulkan (default:gles)"` - TraceTime time.Duration `help:"trace time override (if non-zero)"` - OBB file.Path `help:"path of the subject's obb file"` - obbID string - ObserveFrames uint `help:"Observe the given number of frames. 0 for all (default:0)"` - subjects subject.Subjects -} - -func (v *subjectUploadVerb) Run(ctx context.Context, flags flag.FlagSet) error { - if !v.OBB.IsEmpty() { - if len(flags.Args()) != 1 { - return log.Err(ctx, nil, "Cannot specify multiple subjects with OBB flag") - } - log.I(ctx, "Uploading OBB: %s", v.OBB) - return upload(ctx, []uploadable{path(v.OBB.String()), path(flags.Arg(0))}, v.ServerAddress, v) - } - return upload(ctx, paths(flags.Args()), v.ServerAddress, v) -} -func (v *subjectUploadVerb) prepare(ctx context.Context, conn *grpc.ClientConn) error { - v.subjects = subject.NewRemote(ctx, conn) - return nil -} -func (v *subjectUploadVerb) process(ctx context.Context, id string) error { - if !v.OBB.IsEmpty() && v.obbID == "" { - // obbID always comes first so just set it here. - log.I(ctx, "Uploaded OBB, ID: %s", id) - v.obbID = id - return nil - } - hints := &subject.Hints{} - if v.TraceTime != 0 { - hints.TraceTime = ptypes.DurationProto(v.TraceTime) - } - hints.ObserveFrames = uint32(v.ObserveFrames) - hints.API = v.API.String() - subject, created, err := v.subjects.Add(ctx, id, v.obbID, hints) - if err != nil { - return log.Err(ctx, err, "Failed processing subject") - } - if created { - log.I(ctx, "Added new subject") - } else { - log.I(ctx, "Already existing subject") - } - - log.I(ctx, "Subject info %s", subject) - return nil -} - -type subjectSearchVerb struct { - RobotOptions -} - -func (v *subjectSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - subjects := subject.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return subjects.Search(ctx, expr.Query(), func(ctx context.Context, entry *subject.Subject) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} - -var idOrApkName = script.MustParse("Id == $ or Information.APK.Name == $").Using("$") - -type subjectUpdateVerb struct { - RobotOptions - API APIType `help:"the new api to capture, can be either gles or vulkan"` - TraceTime time.Duration `help:"the new trace time override"` -} - -func (v *subjectUpdateVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - s := subject.NewRemote(ctx, conn) - args := flags.Args() - subj := &subject.Subject{ - Hints: &subject.Hints{ - TraceTime: ptypes.DurationProto(v.TraceTime), - API: v.API.String(), - }, - } - if len(args) == 0 { - return log.Err(ctx, nil, "Missing argument, must specify a subject to update") - } - err := s.Search(ctx, idOrApkName(args[0]).Query(), func(ctx context.Context, entry *subject.Subject) error { - if subj.Id != "" { - return log.Err(ctx, nil, "Multiple subjects matched") - } - subj.Id = entry.Id - return nil - }) - if err != nil { - return err - } - if subj.Id == "" { - return log.Err(ctx, nil, "No packages matched") - } - subj, err = s.Update(ctx, subj) - if err != nil { - return err - } - log.I(ctx, subj.String()) - return nil - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/trace.go b/cmd/robot/trace.go deleted file mode 100644 index 9801d97425..0000000000 --- a/cmd/robot/trace.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "os" - "strings" - - "github.com/golang/protobuf/proto" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/job" - "github.com/google/gapid/test/robot/search/script" - "github.com/google/gapid/test/robot/trace" - "google.golang.org/grpc" -) - -func init() { - uploadVerb.Add(&app.Verb{ - Name: "trace", - ShortHelp: "Upload a gfx trace to the server", - ShortUsage: "", - Action: &traceUploadVerb{RobotOptions: defaultRobotOptions}, - }) - searchVerb.Add(&app.Verb{ - Name: "trace", - ShortHelp: "List build traces in the server", - ShortUsage: "", - Action: &traceSearchVerb{RobotOptions: defaultRobotOptions}, - }) -} - -type traceUploadVerb struct { - RobotOptions - - traces trace.Manager -} - -func (v *traceUploadVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return upload(ctx, paths(flags.Args()), v.ServerAddress, v) -} -func (v *traceUploadVerb) prepare(ctx context.Context, conn *grpc.ClientConn) error { - v.traces = trace.NewRemote(ctx, conn) - return nil -} -func (v *traceUploadVerb) process(ctx context.Context, id string) error { - return v.traces.Update(ctx, "", job.Succeeded, &trace.Output{Trace: id}) -} - -type traceSearchVerb struct { - RobotOptions -} - -func (v *traceSearchVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - traces := trace.NewRemote(ctx, conn) - expression := strings.Join(flags.Args(), " ") - out := os.Stdout - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - return traces.Search(ctx, expr.Query(), func(ctx context.Context, entry *trace.Action) error { - proto.MarshalText(out, entry) - return nil - }) - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/upload.go b/cmd/robot/upload.go deleted file mode 100644 index 54400c0d16..0000000000 --- a/cmd/robot/upload.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/test/robot/stash" - stashgrpc "github.com/google/gapid/test/robot/stash/grpc" - "google.golang.org/grpc" -) - -type uploader interface { - prepare(context.Context, *grpc.ClientConn) error - process(context.Context, string) error -} - -type uploadable interface { - upload(ctx context.Context, client *stash.Client) (string, error) -} - -type path string - -func (p path) upload(ctx context.Context, client *stash.Client) (string, error) { - return client.UploadFile(ctx, file.Abs(string(p))) -} - -func paths(paths []string) []uploadable { - r := make([]uploadable, len(paths)) - for i, p := range paths { - r[i] = path(p) - } - return r -} - -type slice struct { - data []byte - info stash.Upload -} - -func (s slice) upload(ctx context.Context, client *stash.Client) (string, error) { - return client.UploadBytes(ctx, s.info, s.data) -} - -func data(data []byte, name string, executable bool) slice { - return slice{ - data: data, - info: stash.Upload{ - Name: []string{name}, - Executable: executable, - }, - } -} - -func upload(ctx context.Context, uploadables []uploadable, serverAddress string, u uploader) error { - if len(uploadables) == 0 { - app.Usage(ctx, "No files given") - return nil - } - return grpcutil.Client(ctx, serverAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - client, err := stashgrpc.Connect(ctx, conn) - if err != nil { - return err - } - if err := u.prepare(ctx, conn); err != nil { - return err - } - for _, partial := range uploadables { - id, err := partial.upload(ctx, client) - if err != nil { - return log.Err(ctx, err, "Failed calling Upload") - } - log.I(ctx, "Uploaded %s", id) - if err := u.process(ctx, id); err != nil { - return err - } - } - return nil - }, grpc.WithInsecure()) -} diff --git a/cmd/robot/web.go b/cmd/robot/web.go deleted file mode 100644 index 22f147f1d7..0000000000 --- a/cmd/robot/web.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/app/crash" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/test/robot/build" - "github.com/google/gapid/test/robot/job" - "github.com/google/gapid/test/robot/master" - "github.com/google/gapid/test/robot/monitor" - "github.com/google/gapid/test/robot/replay" - "github.com/google/gapid/test/robot/report" - stashgrpc "github.com/google/gapid/test/robot/stash/grpc" - "github.com/google/gapid/test/robot/subject" - "github.com/google/gapid/test/robot/trace" - "github.com/google/gapid/test/robot/web" - "google.golang.org/grpc" -) - -func init() { - startVerb.Add(&app.Verb{ - Name: "web", - ShortHelp: "Starts a robot web server", - Action: &webVerb{ - RobotOptions: defaultRobotOptions, - Port: 8080, - }, - }) -} - -type webVerb struct { - RobotOptions - - Port int `help:"The port to serve the website on"` - Root file.Path `help:"The directory to use as the root of static content"` -} - -func (v *webVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return grpcutil.Client(ctx, v.ServerAddress, func(ctx context.Context, conn *grpc.ClientConn) error { - config := web.Config{ - Port: v.Port, - StaticRoot: v.Root, - Managers: monitor.Managers{ - Master: master.NewRemoteMaster(ctx, conn), - Stash: stashgrpc.MustConnect(ctx, conn), - Build: build.NewRemote(ctx, conn), - Subject: subject.NewRemote(ctx, conn), - Job: job.NewRemote(ctx, conn), - Trace: trace.NewRemote(ctx, conn), - Replay: replay.NewRemote(ctx, conn), - Report: report.NewRemote(ctx, conn), - }, - } - w, err := web.Create(ctx, config) - if err != nil { - return err - } - m := master.NewClient(ctx, config.Master) - restart := false - crash.Go(func() { - shutdown, err := m.Orbit(ctx, master.ServiceList{Worker: true}) - if err != nil { - return - } - restart = shutdown.Restart - w.Close() - }) - log.I(ctx, "Starting web server") - err = w.Serve(ctx) - if restart { - return app.Restart - } - return err - }, grpc.WithInsecure()) -} diff --git a/cmd/shadertool/BUILD.bazel b/cmd/shadertool/BUILD.bazel deleted file mode 100644 index 7630bb11cd..0000000000 --- a/cmd/shadertool/BUILD.bazel +++ /dev/null @@ -1,33 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = ["main.go"], - importpath = "github.com/google/gapid/cmd/shadertool", - visibility = ["//visibility:private"], - deps = [ - "//core/app:go_default_library", - "//core/app/crash:go_default_library", - "//gapis/shadertools:go_default_library", - ], -) - -go_binary( - name = "shadertool", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/cmd/shadertool/main.go b/cmd/shadertool/main.go deleted file mode 100644 index f7f91b1be6..0000000000 --- a/cmd/shadertool/main.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// The shadertool command modifies shader source code. -// For example, it converts GLSL to the desktop dialect. -package main - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "path/filepath" - "sync" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/app/crash" - "github.com/google/gapid/gapis/shadertools" -) - -var ( - out = flag.String("out", "", "Directory for the converted shaders") - check = flag.Bool("check", true, "Verify that the output compiles") - debug = flag.Bool("debug", false, "Make the shader debuggable") - asm = flag.Bool("asm", false, "Print disassembled info") -) - -func main() { - app.Name = "shadertool" - app.ShortHelp = "Converts GLSL ES shader to the desktop GLSL dialect" - app.ShortUsage = "" - app.Run(run) -} - -func run(ctx context.Context) error { - args := flag.Args() - if len(args) == 0 { - flag.Usage() - return nil - } - - // Read input - var wg sync.WaitGroup - for _, input := range args { - input := input - source, err := ioutil.ReadFile(input) - if err != nil { - return err - } - - wg.Add(1) - crash.Go(func() { - defer wg.Done() - - // Process the shader - result, err := convert(string(source), filepath.Ext(input)) - if err != nil { - fmt.Printf("%v: %v\n", input, err) - return - } - - // Write output - if *out == "" { - fmt.Print(result) - } else { - output := filepath.Join(*out, filepath.Base(input)) - err := ioutil.WriteFile(output, []byte(result), 0666) - if err != nil { - fmt.Printf("%v: %v\n", input, err) - return - } - } - }) - } - wg.Wait() - - return nil -} - -func convert(source, shaderType string) (result string, err error) { - opts := shadertools.ConvertOptions{} - switch shaderType { - case ".vert": - opts.ShaderType = shadertools.TypeVertex - case ".frag": - opts.ShaderType = shadertools.TypeFragment - default: - return "", fmt.Errorf("File extension must be .vert or .frag (seen %v)", shaderType) - } - opts.MakeDebuggable = *debug - opts.CheckAfterChanges = *check - opts.Disassemble = *asm - res, err := shadertools.ConvertGlsl(string(source), &opts) - if err != nil { - return "", err - } - if *asm { - result += "/* Disassembly:\n" + res.DisassemblyString + "\n*/\n" - result += "/* Debug info:\n" + shadertools.FormatDebugInfo(res.Info, " ") + "\n*/\n" - } - result += res.SourceCode - return result, nil -} diff --git a/cmd/smoketests/BUILD.bazel b/cmd/smoketests/BUILD.bazel index d0247f9d6c..fc288e4b00 100644 --- a/cmd/smoketests/BUILD.bazel +++ b/cmd/smoketests/BUILD.bazel @@ -21,12 +21,15 @@ go_library( visibility = ["//visibility:private"], deps = [ "//core/app:go_default_library", + "//core/app/layout:go_default_library", "//core/log:go_default_library", + "//core/os/file:go_default_library", ], ) go_binary( name = "smoketests", + data = ["//cmd/gapit"], embed = [":go_default_library"], visibility = ["//visibility:public"], ) diff --git a/cmd/smoketests/main.go b/cmd/smoketests/main.go index a90bccbb33..f2307c251c 100644 --- a/cmd/smoketests/main.go +++ b/cmd/smoketests/main.go @@ -28,11 +28,13 @@ import ( "strings" "github.com/google/gapid/core/app" + "github.com/google/gapid/core/app/layout" "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/file" ) var ( - gapitArg = flag.String("gapit", "gapit", "Path to gapit executable") + gapitArg = flag.String("gapit", "", "Path to gapit executable") keepArg = flag.Bool("keep", false, "Keep the temporary directory even if no errors are found") tracesArg = flag.String("traces", "traces", "The directory containing traces to run smoke tests on") ) @@ -74,13 +76,14 @@ func run(ctx context.Context) error { traceDir = filepath.Join(startwd, traceDir) } - // Gapit path argument - gapitPath := *gapitArg - // We assume gapitPath is found in PATH environment unless it - // contains a separator, in which case we treat it as a path - hasSeparator := strings.ContainsAny(gapitPath, string(filepath.Separator)) - if hasSeparator && !filepath.IsAbs(gapitPath) { - gapitPath = filepath.Join(startwd, gapitPath) + var gapitPath file.Path + if *gapitArg != "" { + gapitPath = file.Abs(*gapitArg) + } else { + gapitPath, err = layout.Gapit(ctx) + if err != nil { + return err + } } // Create temporary directory @@ -114,7 +117,7 @@ func run(ctx context.Context) error { return err } os.Chdir(tracewd) - if err := testTrace(ctx, &nbErr, gapitPath, tracepath); err != nil { + if err := testTrace(ctx, &nbErr, gapitPath.System(), tracepath); err != nil { return err } os.Chdir(startwd) @@ -154,16 +157,12 @@ func testTrace(ctx context.Context, nbErr *int, gapitPath string, tracepath stri tests := [][]string{ {"commands", tracepath}, - {"commands", "-context", "0", tracepath}, - {"commands", "-groupbyapi", tracepath}, - {"commands", "-groupbycontext", tracepath}, {"commands", "-groupbydrawcall", tracepath}, {"commands", "-groupbyframe", tracepath}, - {"commands", "-groupbythread", tracepath}, {"commands", "-groupbyusermarkers", tracepath}, {"commands", "-groupbysubmission", tracepath}, {"commands", "-maxchildren", "1", tracepath}, - {"commands", "-name", "glBindFramebuffer", tracepath}, + {"commands", "-name", "vkQueueSubmit", tracepath}, {"commands", "-observations-ranges", tracepath}, {"commands", "-observations-data", tracepath}, {"commands", "-raw", tracepath}, @@ -194,13 +193,16 @@ func gapit(ctx context.Context, nbErr *int, gapitPath string, args ...string) er printCmd := "gapit " + strings.Join(argsWithoutTrace, " ") + " " + trace // Execute, check error, print status - cmd := exec.Command(gapitPath, args...) + cmd := exec.Command(gapitPath, append(layout.GoArgs(ctx), args...)...) output, err := cmd.CombinedOutput() if err != nil { if _, ok := err.(*exec.ExitError); ok { // Here the gapit command raised an error fmt.Printf("FAIL %s\n", printCmd) - *nbErr += 1 + fmt.Println("===============================================") + fmt.Println(string(output)) + fmt.Println("===============================================") + *nbErr++ } else { // Here the error comes from somewhere else return err diff --git a/cmd/stash/BUILD.bazel b/cmd/stash/BUILD.bazel deleted file mode 100644 index 1cca79239f..0000000000 --- a/cmd/stash/BUILD.bazel +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "main.go", - "search.go", - "server.go", - "upload.go", - ], - importpath = "github.com/google/gapid/cmd/stash", - visibility = ["//visibility:private"], - deps = [ - "//core/app:go_default_library", - "//core/log:go_default_library", - "//core/net/grpcutil:go_default_library", - "//core/os/file:go_default_library", - "//test/robot/search/script:go_default_library", - "//test/robot/stash:go_default_library", - "//test/robot/stash/grpc:go_default_library", - "//test/robot/stash/local:go_default_library", - "@org_golang_google_grpc//:go_default_library", - ], -) - -go_binary( - name = "stash", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/cmd/stash/main.go b/cmd/stash/main.go deleted file mode 100644 index 8fb5c85282..0000000000 --- a/cmd/stash/main.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "net/url" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/test/robot/stash" - _ "github.com/google/gapid/test/robot/stash/grpc" - _ "github.com/google/gapid/test/robot/stash/local" -) - -var ( - stashAddr = "" - defaultStash = "." - defaultStashServer = "//localhost:8091" -) - -func init() { - flag.StringVar(&stashAddr, "stash", "", "The address of the stash") -} - -func main() { - app.ShortHelp = "Stash is a command line tool for interacting with gapid stash servers." - app.Run(app.VerbMain) -} - -type storeTask func(context.Context, *stash.Client) error - -func withStore(ctx context.Context, isServer bool, task storeTask) error { - if stashAddr == "" { - if isServer { - stashAddr = defaultStash - } else { - stashAddr = defaultStashServer - } - } - stashURL, err := url.Parse(stashAddr) - if err != nil { - return log.Errf(ctx, err, "Invalid shash address %s", stashAddr) - } - client, err := stash.Dial(ctx, stashURL) - if err != nil { - return err - } - defer client.Close() - return task(ctx, client) -} diff --git a/cmd/stash/search.go b/cmd/stash/search.go deleted file mode 100644 index 429a66da84..0000000000 --- a/cmd/stash/search.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "os" - "strings" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/test/robot/search/script" - "github.com/google/gapid/test/robot/stash" -) - -func init() { - verb := &app.Verb{ - Name: "search", - ShortHelp: "Prints information about stash entries", - Action: &infoVerb{}, - } - app.AddVerb(verb) -} - -type infoVerb struct { - Monitor bool `help:"Monitor for changes"` -} - -func (v *infoVerb) Run(ctx context.Context, flags flag.FlagSet) error { - return withStore(ctx, false, func(ctx context.Context, client *stash.Client) error { - return getInfo(ctx, client, strings.Join(flags.Args(), " "), v.Monitor) - }) -} - -func getInfo(ctx context.Context, client *stash.Client, expression string, monitor bool) error { - expr, err := script.Parse(ctx, expression) - if err != nil { - return log.Err(ctx, err, "Malformed search query") - } - query := expr.Query() - query.Monitor = monitor - err = client.Search(ctx, query, func(ctx context.Context, entry *stash.Entity) error { - log.I(ctx, "%s", entry) - return nil - }) - if err == nil && monitor { - os.Stdin.Read([]byte{0}) - } - return err -} diff --git a/cmd/stash/server.go b/cmd/stash/server.go deleted file mode 100644 index 958276aaa0..0000000000 --- a/cmd/stash/server.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "net" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/net/grpcutil" - "github.com/google/gapid/test/robot/stash" - stashgrpc "github.com/google/gapid/test/robot/stash/grpc" - "google.golang.org/grpc" -) - -func init() { - verb := &app.Verb{ - Name: "server", - ShortHelp: "Starts a stash server", - Action: &serverVerb{}, - } - app.AddVerb(verb) -} - -type serverVerb struct{} - -func (v *serverVerb) Run(ctx context.Context, flags flag.FlagSet) error { - serveAt := "" - switch flags.NArg() { - case 0: - serveAt = defaultStashServer - case 1: - serveAt = flags.Arg(0) - default: - app.Usage(ctx, "Expected at most 1 arg (the address to server on)") - return nil - } - log.I(ctx, "Starting server on %s", serveAt) - return withStore(ctx, true, func(ctx context.Context, client *stash.Client) error { - return serveStore(ctx, client, serveAt) - }) -} - -func serveStore(ctx context.Context, client *stash.Client, address string) error { - log.I(ctx, "Serving on %s", address) - return grpcutil.Serve(ctx, address, func(ctx context.Context, listener net.Listener, server *grpc.Server) error { - if err := stashgrpc.Serve(ctx, server, client); err != nil { - return err - } - return nil - }) -} diff --git a/cmd/stash/upload.go b/cmd/stash/upload.go deleted file mode 100644 index 5b966610ef..0000000000 --- a/cmd/stash/upload.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/test/robot/stash" -) - -func init() { - verb := &app.Verb{ - Name: "upload", - ShortHelp: "Upload a file to the stash", - Action: &uploadVerb{}, - } - app.AddVerb(verb) -} - -type uploadVerb struct{} - -func (v *uploadVerb) Run(ctx context.Context, flags flag.FlagSet) error { - if flags.NArg() == 0 { - app.Usage(ctx, "No files to upload given") - return nil - } - return withStore(ctx, false, func(ctx context.Context, client *stash.Client) error { - return sendFiles(ctx, client, flags.Args()) - }) -} - -func sendFiles(ctx context.Context, client *stash.Client, filenames []string) error { - for _, partial := range filenames { - id, err := client.UploadFile(ctx, file.Abs(partial)) - if err != nil { - return log.Err(ctx, err, "Failed calling Upload") - } - log.I(ctx, "Uploaded %s as %s", partial, id) - } - return nil -} diff --git a/cmd/update-swt/BUILD.bazel b/cmd/update-swt/BUILD.bazel new file mode 100644 index 0000000000..bfc3f85c52 --- /dev/null +++ b/cmd/update-swt/BUILD.bazel @@ -0,0 +1,18 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "go_default_library", + srcs = ["main.go"], + importpath = "github.com/google/gapid/cmd/update-swt", + visibility = ["//visibility:private"], + deps = [ + "//core/app:go_default_library", + "//core/log:go_default_library", + ], +) + +go_binary( + name = "update-swt", + embed = [":go_default_library"], + visibility = ["//visibility:public"], +) diff --git a/cmd/update-swt/main.go b/cmd/update-swt/main.go new file mode 100644 index 0000000000..9e88f0fee5 --- /dev/null +++ b/cmd/update-swt/main.go @@ -0,0 +1,460 @@ +// Copyright (C) 2019 Google Inc. +// +// 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. + +// This is a very brittle script (see giveUp()) that tries to update the swt.bzl +// and jface.bzl files to upgrade SWT and JFace to a new Eclipse release +// version. To update to a newer version, visit +// https://download.eclipse.org/eclipse/downloads/, choose your version and +// click on it. The version to pass to this script is the last URL segment and +// should take the form "[RS]--". +// This tool uses a bunch of regex's to parse HTML and the .bzl files, and does +// so in a rigid, but defensive manner. This means that trivial changes to the +// Eclipse HTML or the .bzl files will likely make the parsing fail, but at +// least if it succeeds, it's fairly confident that what it got is what it +// thinks it got. +package main + +import ( + "bytes" + "context" + "crypto/sha256" + "flag" + "fmt" + "io/ioutil" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/log" +) + +var ( + editSWT = flag.Bool("swt", true, "Edit swt.bzl") + editJFace = flag.Bool("jface", true, "Edit jface.bzl") +) + +const ( + labelLinux = "Linux (64 bit version)" + labelWindows = "Windows (64 bit version)" + labelMacOS = "Mac OSX (64 bit version)" + swtBzl = "tools/build/third_party/swt.bzl" + jfaceBzl = "tools/build/third_party/jface.bzl" +) + +var ( + repoBase, _ = url.Parse("https://download.eclipse.org/eclipse/downloads/drops4/") + repoTableRegex = h3TableRegex("Repository") + swtTableRegex = h3TableRegex("SWT") + tableRowRegex = regexp.MustCompile("(?s:]*)?>(.*?))") + tableCellRegex = regexp.MustCompile("(?s:]*)?>(.*?))") + linkRegex1 = regexp.MustCompile("]*)? href=\"([^\"]+)\"") + linkRegex2 = regexp.MustCompile("(.*?)") + pluginsRegex = divRegex("dirlist") + swtBzlRegex = regexp.MustCompile("(?m:^_(URL|SHA)_(LINUX|WIN|OSX) = \"([^\"]*)\"$)") + jfaceBaseRegex = regexp.MustCompile("(?m:^_BASE = \"[^\"]*\"$)") + jfaceBzlRegex = regexp.MustCompile( + "(?s:struct\\(\\s*name = \"([^\"]+)\"," + + "\\s*version = \"([^\"]+)\"," + + "\\s*sha = \"([^\"]+)\"," + + "\\s*sha_src = \"([^\"]+)\"," + + ")") +) + +func main() { + app.ShortHelp = "update-swt updates the SWT and JFace repository rules" + app.ShortUsage = "" + app.Name = "update-swt" + app.Run(run) +} + +func run(ctx context.Context) error { + if flag.NArg() != 1 { + app.Usage(ctx, "Exactly one version expected, got %d", flag.NArg()) + return nil + } + version := flag.Args()[0] + + repo, err := getRepo(ctx, version) + if err != nil { + return log.Errf(ctx, err, "Failed to get repo for version '%s'", version) + } + + if *editSWT { + if err := repo.editSWTBazel(ctx); err != nil { + return log.Errf(ctx, err, "Failed to edit the swt.bzl file") + } + } + + if *editJFace { + if err := repo.editJFaceBazel(ctx); err != nil { + return log.Errf(ctx, err, "Failed to edit the jface.bzl file") + } + } + + return nil +} + +func giveUp(ctx context.Context, cause error, fmt string, args ...interface{}) error { + log.E(ctx, "My input parsing is very strict and some HTML may have changed. I give up.") + return log.Errf(ctx, cause, fmt, args...) +} + +type repo struct { + base *url.URL + swtLinux *url.URL + swtWindows *url.URL + swtMacOS *url.URL +} + +func getRepo(ctx context.Context, version string) (*repo, error) { + repoUrl, err := repoBase.Parse(url.PathEscape(version) + "/") + if err != nil { + return nil, log.Errf(ctx, err, "Version '%s' caused an invalid URL", version) + } + html, err := fetchString(ctx, repoUrl.String()) + if err != nil { + return nil, log.Errf(ctx, err, "Failed to download repo page") + } + + repoTable, err := extractTable(ctx, html, repoTableRegex) + if err != nil { + return nil, giveUp(ctx, err, "Failed to extract repository table") + } + if len(repoTable) != 2 || len(repoTable[1]) != 1 { + return nil, giveUp(ctx, err, "Repo table has unexpected dimensions (%v)", repoTable) + } + base, err := extractLink(ctx, repoUrl, repoTable[1][0]) + if err != nil { + return nil, giveUp(ctx, err, "Failed to extract repo URL") + } + + r := &repo{base: base} + + swtTable, err := extractTable(ctx, html, swtTableRegex) + if err != nil { + return nil, giveUp(ctx, err, "Failed to extract SWT table") + } + if len(swtTable) < 2 { + return nil, giveUp(ctx, err, "SWT table has not enough rows (%v)", swtTable) + } + for _, row := range swtTable[1:] { // skip over the table headers + if len(row) != 3 { + return nil, giveUp(ctx, err, "SWT table has unexpected dimensions (%v)", swtTable) + } + link, err := extractLink(ctx, repoUrl, row[1]) + if err != nil { + return nil, giveUp(ctx, err, "Failed to extract SWT URL") + } + file := link.Query().Get("dropFile") + if file == "" { + return nil, giveUp(ctx, nil, "URL %v didn't contain a dropFile query parameter", link) + } + link, err = link.Parse(file) + if err != nil { + return nil, giveUp(ctx, err, "Drop file '%s' caused an invalid URL", file) + } + + switch row[0] { + case labelLinux: + r.swtLinux = link + case labelWindows: + r.swtWindows = link + case labelMacOS: + r.swtMacOS = link + default: + log.I(ctx, "Ignoring unknown OS '%s'", row[0]) + + } + } + + if r.swtLinux == nil || r.swtWindows == nil || r.swtMacOS == nil { + return nil, giveUp(ctx, nil, "Didn't find SWT for every OS: %v %v %v", r.swtLinux, r.swtWindows, r.swtMacOS) + } + + return r, nil +} + +func (r *repo) editSWTBazel(ctx context.Context) (err error) { + data := map[string]*struct { + url string + sha string + done int + }{ + "LINUX": {r.swtLinux.String(), "", 0}, + "WIN": {r.swtWindows.String(), "", 0}, + "OSX": {r.swtMacOS.String(), "", 0}, + } + for os, d := range data { + d.sha, err = sha(ctx, d.url) + if err != nil { + return log.Errf(ctx, err, "Getting SHA for SWT for OS %s", os) + } + } + + src, err := ioutil.ReadFile(swtBzl) + if err != nil { + return giveUp(ctx, err, "Failed to read swt.bzl at %s", swtBzl) + } + matches := swtBzlRegex.FindAllSubmatchIndex(src, -1) + if len(matches) != 6 { + return giveUp(ctx, err, "Failed to match the swt.bzl regex (%b)", matches) + } + + var b bytes.Buffer + last := 0 + for _, loc := range matches { + b.Write(src[last:loc[0]]) + last = loc[1] + + ty := string(src[loc[2]:loc[3]]) + os := string(src[loc[4]:loc[5]]) + if d, ok := data[os]; ok { + b.WriteString("_" + ty + "_" + os + " = \"") + switch ty { + case "URL": + b.WriteString(d.url) + d.done |= 1 + case "SHA": + b.WriteString(d.sha) + d.done |= 2 + } + b.WriteString("\"") + } + } + b.Write(src[last:]) + + for os, d := range data { + if d.done != 3 { + return giveUp(ctx, nil, "Didn't find SHA/URL for OS %s (%d)", os, d.done) + } + } + if err := ioutil.WriteFile(swtBzl, b.Bytes(), 0644); err != nil { + return giveUp(ctx, err, "Failed to write output to swt.bzl") + } + + log.I(ctx, "I've updated %s successfully... I think.", swtBzl) + return nil +} + +func (r *repo) editJFaceBazel(ctx context.Context) error { + base, _ := r.base.Parse("plugins/") + html, err := fetchString(ctx, base.String()) + if err != nil { + return log.Errf(ctx, err, "Failed fetching the plugins repo") + } + div, err := extractDiv(ctx, html, pluginsRegex) + if err != nil { + return giveUp(ctx, err, "Failed to extract plugins file list") + } + links, err := extractLinks(ctx, base, div) + if err != nil { + return giveUp(ctx, err, "Failed to extract plugins links") + } + links = filterJars(links) + + src, err := ioutil.ReadFile(jfaceBzl) + if err != nil { + return giveUp(ctx, err, "Failed to read jface.bzl at %s", jfaceBzl) + } + + matches := jfaceBaseRegex.FindAllIndex(src, -1) + if len(matches) != 1 { + return giveUp(ctx, err, "Failed to match the base JFace regex (%v)", matches) + } + src = append(src[:matches[0][0]], + append([]byte("_BASE = \""+base.String()+"\""), src[matches[0][1]:]...)...) + + matches = jfaceBzlRegex.FindAllSubmatchIndex(src, -1) + if len(matches) == 0 { + return giveUp(ctx, nil, "Failed to match the JFace regex (%v)", matches) + } + if len(matches) != 8 { + log.W(ctx, "I found %d JFace jars, but I expected 8. Closing my eyes and continuing...", len(matches)) + } + + var b bytes.Buffer + last := 0 + for _, loc := range matches { + b.Write(src[last:loc[0]]) + last = loc[1] + + name := string(src[loc[2]:loc[3]]) + var jar, srcJar *link + for i := range links { + if strings.HasPrefix(links[i].label, name+"_") { + jar = &links[i] + } else if strings.HasPrefix(links[i].label, name+".source_") { + srcJar = &links[i] + } + } + if jar == nil || srcJar == nil { + return giveUp(ctx, nil, "Didn't find jar/src jar for %s", name) + } + version := jar.label[len(name)+1 : len(jar.label)-4] + jarSha, err := sha(ctx, jar.url.String()) + if err != nil { + return giveUp(ctx, err, "Failed to get SHA for JAR %s", jar.label) + } + srcSha, err := sha(ctx, srcJar.url.String()) + if err != nil { + return giveUp(ctx, err, "Failed to get SHA for src JAR %s", srcJar.label) + } + fmt.Println(jarSha + " " + srcSha) + b.Write(src[loc[0]:loc[4]]) + b.WriteString(version) + b.Write(src[loc[5]:loc[6]]) + b.WriteString(jarSha) + b.Write(src[loc[7]:loc[8]]) + b.WriteString(srcSha) + b.Write(src[loc[9]:loc[1]]) + } + b.Write(src[last:]) + + if err := ioutil.WriteFile(jfaceBzl, b.Bytes(), 0644); err != nil { + return giveUp(ctx, err, "Failed to write output to jface.bzl") + } + + log.I(ctx, "I've updated %s successfully... I think.", jfaceBzl) + return nil +} + +func h3TableRegex(id string) *regexp.Regexp { + return regexp.MustCompile("(?s:

.*?]*>(.*?)
)") +} + +func divRegex(id string) *regexp.Regexp { + return regexp.MustCompile("(?s:
]*)?>(.*?)
)") +} + +func fetch(ctx context.Context, url string) ([]byte, error) { + log.I(ctx, "Fetching %s...", url) + resp, err := http.Get(url) + if err != nil { + return nil, giveUp(ctx, err, "Failed to download '%s'", url) + } + defer resp.Body.Close() + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return nil, giveUp(ctx, err, "Got a failure response (%d) from '%s'", resp.StatusCode, url) + } + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, giveUp(ctx, err, "Failed to read response from '%s'", url) + } + return data, nil +} + +func fetchString(ctx context.Context, url string) (string, error) { + data, err := fetch(ctx, url) + if err != nil { + return "", err + } + return string(data), nil +} + +func sha(ctx context.Context, url string) (string, error) { + data, err := fetch(ctx, url) + if err != nil { + return "", log.Errf(ctx, err, "Failed to fetch content to compute SHA") + } + return fmt.Sprintf("%x", sha256.Sum256(data)), nil +} + +func extractTable(ctx context.Context, html string, re *regexp.Regexp) ([][]string, error) { + tables := re.FindAllStringSubmatch(html, -1) + if len(tables) != 1 { + log.I(ctx, "%s", html) + return nil, log.Errf(ctx, nil, "Table regex did not match (%v)", tables) + } + + rows := tableRowRegex.FindAllStringSubmatch(tables[0][1], -1) + if len(rows) == 0 { + log.I(ctx, "%s", tables[0][1]) + return nil, log.Errf(ctx, nil, "No rows found in table") + } + + r := [][]string{} + for _, row := range rows { + cells := tableCellRegex.FindAllStringSubmatch(row[1], -1) + if len(cells) == 0 { + log.I(ctx, "%s", row[1]) + return nil, log.Errf(ctx, nil, "No cells found in row") + } + c := make([]string, len(cells)) + for i, cell := range cells { + c[i] = cell[1] + } + r = append(r, c) + } + + return r, nil +} + +func extractDiv(ctx context.Context, html string, re *regexp.Regexp) (string, error) { + div := re.FindAllStringSubmatch(html, -1) + if len(div) != 1 { + log.I(ctx, "%s", html) + return "", log.Errf(ctx, nil, "Div regex did not match (%v)", div) + } + return div[0][1], nil +} + +func extractLink(ctx context.Context, base *url.URL, html string) (*url.URL, error) { + matches := linkRegex1.FindAllStringSubmatch(html, -1) + if len(matches) != 1 { + log.I(ctx, "%s", html) + return nil, log.Errf(ctx, nil, "Link regex did not match (%v)", matches) + } + + u, err := base.Parse(matches[0][1]) + if err != nil { + return nil, log.Errf(ctx, err, "Extracted invalid url") + } + return u, nil +} + +type link struct { + url *url.URL + label string +} + +func extractLinks(ctx context.Context, base *url.URL, html string) ([]link, error) { + matches := linkRegex2.FindAllStringSubmatch(html, -1) + if len(matches) == 0 { + log.I(ctx, "%s", html) + return nil, log.Errf(ctx, nil, "Links regex did not match (%v)", matches) + } + + r := []link{} + for _, match := range matches { + u, err := base.Parse(match[1]) + if err != nil { + return nil, log.Errf(ctx, err, "Extracted invalid url") + } + r = append(r, link{u, strings.TrimSpace(match[2])}) + } + return r, nil +} + +func filterJars(links []link) []link { + r := []link{} + for _, l := range links { + if strings.HasSuffix(l.label, ".jar") { + r = append(r, l) + } + } + return r +} diff --git a/cmd/verify_gles_api/BUILD.bazel b/cmd/verify_gles_api/BUILD.bazel deleted file mode 100644 index fc701c468e..0000000000 --- a/cmd/verify_gles_api/BUILD.bazel +++ /dev/null @@ -1,37 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "main.go", - "registry.go", - ], - importpath = "github.com/google/gapid/cmd/verify_gles_api", - visibility = ["//visibility:private"], - deps = [ - "//core/app:go_default_library", - "//core/text/parse/cst:go_default_library", - "//gapil:go_default_library", - "//gapil/semantic:go_default_library", - ], -) - -go_binary( - name = "verify_gles_api", - embed = [":go_default_library"], - visibility = ["//visibility:public"], -) diff --git a/cmd/verify_gles_api/main.go b/cmd/verify_gles_api/main.go deleted file mode 100644 index 665a4382f4..0000000000 --- a/cmd/verify_gles_api/main.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "context" - "flag" - "fmt" - "os" - "regexp" - "strconv" - "strings" - - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/text/parse/cst" - "github.com/google/gapid/gapil" - "github.com/google/gapid/gapil/semantic" -) - -var ( - apiPath = flag.String("api", "", "Filename of the api file to verify (required)") - cacheDir = flag.String("cache", "", "Directory for caching downloaded files (required)") - apiRoot *semantic.API - mappings *semantic.Mappings - numErrors = 0 -) - -func main() { - app.ShortHelp = "stringgen compiles string table files to string packages and a Go definition file." - app.Run(run) -} - -func run(ctx context.Context) error { - if *apiPath == "" { - app.Usage(ctx, "Mustsupply api path") - } - if *cacheDir == "" { - app.Usage(ctx, "Must supply cache dir") - } - processor := gapil.NewProcessor() - mappings = processor.Mappings - api, errs := processor.Resolve(*apiPath) - if len(errs) > 0 { - for _, err := range errs { - PrintError("%v\n", err.Message) - } - os.Exit(2) - } - apiRoot = api - reg := DownloadRegistry() - VerifyApi(reg) - return nil -} - -func PrintError(format string, a ...interface{}) { - fmt.Fprintf(os.Stderr, format, a...) - numErrors = numErrors + 1 -} - -func VerifyApi(reg *Registry) { - VerifyEnum(reg, false) - VerifyEnum(reg, true) - for _, cmd := range reg.Command { - VerifyCommand(reg, cmd) - } -} - -func VerifyEnum(r *Registry, bitfields bool) { - name := "GLenum" - if bitfields { - name = "GLbitfield" - } - expected := make(map[string]struct{}) - for _, enums := range r.Enums { - if (enums.Type == "bitmask") == bitfields && enums.Namespace == "GL" { - for _, enum := range enums.Enum { - // Boolean values are handles specially (as labels) - if enum.Name == "GL_TRUE" || enum.Name == "GL_FALSE" { - continue - } - // The following 64bit values are not proper GLenum values. - if enum.Name == "GL_TIMEOUT_IGNORED" || enum.Name == "GL_TIMEOUT_IGNORED_APPLE" { - continue - } - if enum.API == "" || enum.API == GLES1API || enum.API == GLES2API { - var value uint32 - if v, err := strconv.ParseUint(enum.Value, 0, 32); err == nil { - value = uint32(v) - } else if v, err := strconv.ParseInt(enum.Value, 0, 32); err == nil { - value = uint32(v) - } else { - PrintError("Failed to parse enum value %v", enum.Value) - continue - } - expected[fmt.Sprintf("%s = 0x%08X", enum.Name, value)] = struct{}{} - } - } - } - } - seen := make(map[string]struct{}) - for _, e := range apiRoot.Enums { - if e.Name() == name { - for _, m := range e.Entries { - seen[fmt.Sprintf("%s = 0x%08X", m.Name(), m.Value)] = struct{}{} - } - } - } - CompareSets(expected, seen, name+": ") -} - -func CompareSets(expected, seen map[string]struct{}, msg_prefix string) { - for k := range expected { - if _, found := seen[k]; !found { - PrintError("%sMissing %s\n", msg_prefix, k) - } - } - for k := range seen { - if _, found := expected[k]; !found { - PrintError("%sUnexpected %s\n", msg_prefix, k) - } - } - return -} - -var re_const_ptr_pre = regexp.MustCompile(`^const (\w+) \*$`) -var re_const_ptr_post = regexp.MustCompile(`^(.+)\bconst\*$`) - -func VerifyType(cmd string, paramIndex int, expected string, seen semantic.Type) bool { - expected = strings.TrimSpace(expected) - name := seen.(semantic.NamedNode).Name() - switch s := seen.(type) { - case *semantic.Pointer: - if s.Const { - if match := re_const_ptr_pre.FindStringSubmatch(expected); match != nil { - return VerifyType(cmd, paramIndex, match[1], s.To) - } - if match := re_const_ptr_post.FindStringSubmatch(expected); match != nil { - return VerifyType(cmd, paramIndex, match[1], s.To) - } - } else { - if strings.HasSuffix(expected, "*") { - return VerifyType(cmd, paramIndex, strings.TrimSuffix(expected, "*"), s.To) - } - } - case *semantic.Pseudonym: - if s.Name() == expected { - return true - } else if expected == "GLDEBUGPROCKHR" && s.Name() == "GLDEBUGPROC" { - return true - } else { - return VerifyType(cmd, paramIndex, expected, s.To) - } - case *semantic.Enum: - if s.Name() == expected { - return true - } - case *semantic.Builtin: - if s.Name() == expected { - return true - } else if expected == "const GLchar *" && s.Name() == "string" { - return true - } - } - PrintError("%s: Param %v: Expected type %s but seen %s (%T)\n", cmd, paramIndex, expected, name, seen) - return false -} - -func UniqueStrings(strs []string) (res []string) { - seen := map[string]struct{}{} - for _, str := range strs { - if _, ok := seen[str]; !ok { - res = append(res, str) - seen[str] = struct{}{} - } - } - return -} - -func VerifyCommand(reg *Registry, cmd *Command) { - cmdName := cmd.Name() - versions := append(reg.GetVersions(GLES1API, cmdName), reg.GetVersions(GLES2API, cmdName)...) - extensions := append(reg.GetExtensions(GLES1API, cmdName), reg.GetExtensions(GLES2API, cmdName)...) - extensions = UniqueStrings(extensions) - if len(versions) == 0 && len(extensions) == 0 { - return // It is not a GLES command. - } - - // Expected annotations. - annots := []string{} - if strings.HasPrefix(cmdName, "glDraw") && !strings.HasPrefix(cmdName, "glDrawBuffers") { - annots = append(annots, "@draw_call") - } - for _, version := range versions { - if version == Version("1.0") && len(versions) > 1 { - continue // TODO: Add those in the api file. - } - url, _ := GetCoreManpage(version, cmdName) - annots = append(annots, fmt.Sprintf(`@doc("%s", Version.GLES%v)`, url, strings.Replace(string(version), ".", "", -1))) - } - for _, extension := range extensions { - url, _ := GetExtensionManpage(extension) - annots = append(annots, fmt.Sprintf(`@doc("%s", Extension.%v)`, url, extension)) - } - if len(versions) != 0 { - cond := fmt.Sprintf(`Version.GLES%v`, strings.Replace(string(versions[0]), ".", "", -1)) - if len(versions) == 1 && versions[0] == Version("1.0") { - cond = "Version.GLES10 && !Version.GLES20" // Deprecated in GLES20 - } - annots = append(annots, fmt.Sprintf(`@if(%v)`, cond)) - } - if len(extensions) != 0 { - conds := []string{} - for _, extension := range extensions { - conds = append(conds, fmt.Sprintf(`Extension.%v`, extension)) - } - annots = append(annots, fmt.Sprintf("@if(%s)", strings.Join(conds, " || "))) - } - - // Find existing API function. - var apiCmd *semantic.Function - for _, apiFunction := range apiRoot.Functions { - if apiFunction.Name() == cmdName { - apiCmd = apiFunction - } - } - - // Print command to stdout if it is missing. - if apiCmd == nil { - params := []string{} - for _, param := range cmd.Param { - params = append(params, param.Type()+" "+param.Name) - } - fmt.Printf("%s\ncmd %s %s(%s) { }\n", strings.Join(annots, "\n"), - cmd.Proto.Type(), cmdName, strings.Join(params, ", ")) - return - } - - // Check documentation strings. - expected := make(map[string]struct{}) - for _, a := range annots { - expected[a] = struct{}{} - } - seen := make(map[string]struct{}) - for _, a := range apiCmd.Annotations { - if a.Name() == "if" || a.Name() == "doc" || a.Name() == "draw_call" { - seen[getSource(mappings.AST.CST(a.AST))] = struct{}{} - } - } - CompareSets(expected, seen, fmt.Sprintf("%s: ", cmdName)) - - // Check parameter types. - if len(cmd.Param) != len(apiCmd.CallParameters()) { - PrintError("%s: Expected %v parameters but seen %v\n", cmdName, len(cmd.Param), len(apiCmd.CallParameters())) - } else { - for i, p := range cmd.Param { - VerifyType(cmdName, i, p.Type(), apiCmd.FullParameters[i].Type) - } - } -} - -func getSource(n cst.Node) string { - return string(n.Tok().Source.Runes[n.Tok().Start:n.Tok().End]) -} diff --git a/cmd/verify_gles_api/registry.go b/cmd/verify_gles_api/registry.go deleted file mode 100644 index c33b7a2438..0000000000 --- a/cmd/verify_gles_api/registry.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package main - -import ( - "encoding/xml" - "fmt" - "io/ioutil" - "net/http" - "os" - "regexp" - "strings" -) - -const registry_url = "https://cvs.khronos.org/svn/repos/ogl/trunk/doc/registry/public/api/gl.xml" - -// DownloadRegistry downloads the Khronos XML registry file. -func DownloadRegistry() *Registry { - bytes := Download(registry_url) - if len(bytes) == 0 { - panic(fmt.Errorf("Can not download %s", registry_url)) - } - reg := &Registry{} - if err := xml.Unmarshal(bytes, reg); err != nil { - panic(err.Error()) - } - return reg -} - -type KhronosAPI string -type Version string - -const GLES1API = KhronosAPI("gles1") -const GLES2API = KhronosAPI("gles2") // Includes GLES 3.0 and later - -func (v Version) String() string { return fmt.Sprintf("%s", string(v)) } - -type Registry struct { - Group []*Group `xml:"groups>group"` - Enums []*Enums `xml:"enums"` - Command []*Command `xml:"commands>command"` - Feature []*Feature `xml:"feature"` - Extension []*ExtensionElement `xml:"extensions>extension"` -} - -type NamedElementList []NamedElement -type NamedElement struct { - Name string `xml:"name,attr"` -} - -type Group struct { - NamedElement - Enum NamedElementList `xml:"enum"` -} - -type Enums struct { - Namespace string `xml:"namespace,attr"` - Group string `xml:"group,attr"` - Type string `xml:"type,attr"` // "bitmask" - Comment string `xml:"comment,attr"` - Enum []Enum `xml:"enum"` -} - -type Enum struct { - NamedElement - Value string `xml:"value,attr"` - Type string `xml:"type,attr"` // "u" or "ull" - API KhronosAPI `xml:"api,attr"` - Alias string `xml:"alias,attr"` -} - -type Command struct { - Proto ProtoOrParam `xml:"proto"` - Param []ProtoOrParam `xml:"param"` - Alias NamedElement `xml:"alias"` -} - -type ProtoOrParam struct { - InnerXML string `xml:",innerxml"` - Chardata string `xml:",chardata"` - Group string `xml:"group,attr"` - Length string `xml:"len,attr"` - Ptype string `xml:"ptype"` - Name string `xml:"name"` -} - -type Feature struct { - NamedElement - API KhronosAPI `xml:"api,attr"` - Number Version `xml:"number,attr"` - Require RequireOrRemoveList `xml:"require"` - Remove RequireOrRemoveList `xml:"remove"` -} - -type ExtensionElement struct { - NamedElement - Supported string `xml:"supported,attr"` - Require RequireOrRemoveList `xml:"require"` - Remove RequireOrRemoveList `xml:"remove"` -} - -type RequireOrRemoveList []RequireOrRemove -type RequireOrRemove struct { - API KhronosAPI `xml:"api,attr"` // for extensions only - Profile string `xml:"profile,attr"` - Comment string `xml:"comment,attr"` - Enum NamedElementList `xml:"enum"` - Command NamedElementList `xml:"command"` -} - -func (l NamedElementList) Contains(name string) bool { - for _, v := range l { - if v.Name == name { - return true - } - } - return false -} - -func (r *RequireOrRemove) Contains(name string) bool { - return r.Enum.Contains(name) || r.Command.Contains(name) -} - -func (l RequireOrRemoveList) Contains(name string) bool { - for _, v := range l { - if v.Contains(name) { - return true - } - } - return false -} - -func (e *ExtensionElement) IsSupported(api KhronosAPI) bool { - for _, v := range strings.Split(e.Supported, "|") { - if KhronosAPI(v) == api { - return true - } - } - return false -} - -func (c Command) Name() string { - return c.Proto.Name -} - -func (p ProtoOrParam) Type() string { - name := p.InnerXML - name = name[:strings.Index(name, "")] - name = strings.Replace(name, "", "", 1) - name = strings.Replace(name, "", "", 1) - name = strings.TrimSpace(name) - return name -} - -// ParamsAndResult returns all parameters and return value as -1. -func (cmd *Command) ParamsAndResult() map[int]ProtoOrParam { - result := cmd.Proto - result.Name = "result" - results := map[int]ProtoOrParam{-1: result} - for i, param := range cmd.Param { - results[i] = param - } - return results -} - -// GetVersions returns sorted list of versions which support the given symbol. -func (r *Registry) GetVersions(api KhronosAPI, name string) []Version { - version, found := Version(""), false - for _, feature := range r.Feature { - if feature.API == api { - if feature.Require.Contains(name) { - if found { - panic(fmt.Errorf("redefinition of %s", name)) - } - version, found = feature.Number, true - } - if feature.Remove != nil { - // not used in GLES - panic(fmt.Errorf("remove tag is not supported")) - } - } - } - if found { - switch version { - case "1.0": - return []Version{"1.0"} - case "2.0": - return []Version{"2.0", "3.0", "3.1", "3.2"} - case "3.0": - return []Version{"3.0", "3.1", "3.2"} - case "3.1": - return []Version{"3.1", "3.2"} - case "3.2": - return []Version{"3.2"} - default: - panic(fmt.Errorf("Uknown GLES version: %v", version)) - } - } else { - return nil - } -} - -// GetExtensions returns extensions which define the given symbol. -func (r *Registry) GetExtensions(api KhronosAPI, name string) []string { - var extensions []string -ExtensionLoop: - for _, extension := range r.Extension { - if extension.IsSupported(api) { - for _, require := range extension.Require { - if require.API == "" || require.API == api { - if require.Contains(name) { - extensions = append(extensions, extension.Name) - // sometimes the extension repeats definition - ignore - continue ExtensionLoop - } - } - } - if extension.Remove != nil { - // not used in GLES - panic(fmt.Errorf("remove tag is not supported")) - } - } - } - return extensions -} - -var sufix_re = regexp.MustCompile("(64|)(i_|)(I|)([1-4]|[1-4]x[1-4]|)(x|ub|f|i|ui|fi|i64|)(v|)$") - -func GetCoreManpage(version Version, cmdName string) (url string, data []byte) { - var urlFormat string - switch version { - case "1.0": - urlFormat = "https://www.khronos.org/opengles/sdk/1.1/docs/man/%s.xml" - case "2.0": - urlFormat = "https://www.khronos.org/opengles/sdk/docs/man/xhtml/%s.xml" - case "3.0": - urlFormat = "https://www.khronos.org/opengles/sdk/docs/man3/html/%s.xhtml" - case "3.1": - urlFormat = "https://www.khronos.org/opengles/sdk/docs/man31/html/%s.xhtml" - case "3.2": - urlFormat = "https://www.khronos.org/opengles/sdk/docs/man32/html/%s.xhtml" - default: - panic(fmt.Errorf("Uknown api version: %v", version)) - } - - for _, table := range []struct{ oldPrefix, newPrefix string }{ - {"glDisable", "glEnable"}, - {"glEnd", "glBegin"}, - {"glGetBoolean", "glGet"}, - {"glGetFixed", "glGet"}, - {"glGetFloat", "glGet"}, - {"glGetInteger", "glGet"}, - {"glGetnUniform", "glGetUniform"}, - {"glMemoryBarrierByRegion", "glMemoryBarrier"}, - {"glProgramUniformMatrix", "glProgramUniform"}, - {"glReadnPixels", "glReadPixels"}, - {"glUniformMatrix", "glUniform"}, - {"glUnmapBuffer", "glMapBufferRange"}, - {"glVertexAttribIFormat", "glVertexAttribFormat"}, - {"glVertexAttribIPointer", "glVertexAttribPointer"}, - {"", ""}, // no-op - } { - if strings.HasPrefix(cmdName, table.oldPrefix) { - // Replace prefix - cmdName := table.newPrefix + strings.TrimPrefix(cmdName, table.oldPrefix) - // Try to download URL without suffix - if sufix_re.MatchString(cmdName) { - cmdName := sufix_re.ReplaceAllString(cmdName, "") - url = fmt.Sprintf(urlFormat, cmdName) - if data := Download(url); len(data) > 0 { - return url, data - } - } - // Try to download URL with suffix - url = fmt.Sprintf(urlFormat, cmdName) - if data := Download(url); len(data) > 0 { - return url, data - } - } - } - panic(fmt.Errorf("Failed to find URL for %s", cmdName)) -} - -func GetExtensionManpage(extension string) (url string, data []byte) { - parts := strings.Split(extension, "_") - vendor := parts[1] - page := strings.Join(parts[2:], "_") - url = fmt.Sprintf("https://www.khronos.org/registry/gles/extensions/%s/%s_%s.txt", vendor, vendor, page) - if data := Download(url); len(data) > 0 { - return url, data - } - for _, table := range []struct{ extension, page string }{ - {"GL_NV_coverage_sample", "NV/EGL_NV_coverage_sample.txt"}, - {"GL_NV_depth_nonlinear", "NV/EGL_NV_depth_nonlinear.txt"}, - {"GL_NV_EGL_NV_coverage_sample", "NV/EGL_NV_coverage_sample.txt"}, - {"GL_EXT_separate_shader_objects", "EXT/EXT_separate_shader_objects.gles.txt"}, - } { - if table.extension == extension { - url = fmt.Sprintf("https://www.khronos.org/registry/gles/extensions/%s", table.page) - if data := Download(url); len(data) > 0 { - return url, data - } - } - } - panic(fmt.Errorf("Failed to find URL for %s", extension)) -} - -// Download the given URL. Returns empty slice if the page can not be found (404). -func Download(url string) []byte { - filename := url - filename = strings.TrimPrefix(filename, "https://") - filename = strings.Replace(filename, "/", "-", strings.Count(filename, "/")-1) - filename = strings.Replace(filename, "/", string(os.PathSeparator), 1) - filename = *cacheDir + string(os.PathSeparator) + filename - if bytes, err := ioutil.ReadFile(filename); err == nil { - return bytes - } - resp, err := http.Get(url) - if err != nil { - panic(err) - } - bytes := []byte{} - if resp.StatusCode == 200 { - bytes, err = ioutil.ReadAll(resp.Body) - if err != nil { - panic(err) - } - } else if resp.StatusCode != 404 { - panic(fmt.Errorf("%s: %s", url, resp.Status)) - } - resp.Body.Close() - dir := filename[0:strings.LastIndex(filename, string(os.PathSeparator))] - if err := os.MkdirAll(dir, 0750); err != nil { - panic(err) - } - if err := ioutil.WriteFile(filename, bytes, 0666); err != nil { - panic(err) - } - return bytes -} diff --git a/cmd/vulkan_sample/BUILD.bazel b/cmd/vulkan_sample/BUILD.bazel index e38b4b3acd..d2023177c0 100644 --- a/cmd/vulkan_sample/BUILD.bazel +++ b/cmd/vulkan_sample/BUILD.bazel @@ -19,10 +19,9 @@ cc_library( srcs = [ "cube.h", "frag.h", - "icon.h", "main.cpp", "vert.h", - ], + ] + ["//tools/logo:logo_256_h"], copts = cc_copts(), linkopts = select({ "//tools/build:linux": [ @@ -30,6 +29,7 @@ cc_library( "-lpthread", # Needed to run on Swiftshader (Nov. 2019) ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": [], # Android "//conditions:default": [ @@ -37,7 +37,6 @@ cc_library( "-ldl", "-llog", "-lm", - "-pthread", ], }), deps = [ @@ -46,6 +45,7 @@ cc_library( "//tools/build:linux": [], "//tools/build:windows": [], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], # Android "//conditions:default": [ "@android_native_app_glue//:native_app_glue", diff --git a/cmd/vulkan_sample/icon.h b/cmd/vulkan_sample/icon.h deleted file mode 100644 index 5b267e78f9..0000000000 --- a/cmd/vulkan_sample/icon.h +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ - -// You can use convert_img_to_c in github.com/google/vulkan_test_applications -// to regenerate this file. -// clang-format off - -const struct { - VkFormat format; - size_t width; size_t height; struct {uint8_t r; uint8_t g; uint8_t b; uint8_t a;} data[65536]; -} texture = { - VK_FORMAT_R8G8B8A8_UNORM, - 256, - 256, - { - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {73, 73, 109, 7}, {73, 91, 109, 14}, {71, 85, 99, 18}, {85, 97, 109, 21}, {70, 81, 93, 22}, {78, 88, 98, 26}, {73, 91, 100, 28}, {77, 85, 94, 30}, {77, 85, 94, 30}, {76, 94, 94, 27}, {82, 92, 102, 25}, {70, 81, 93, 22}, {77, 89, 89, 20}, {71, 85, 99, 18}, {73, 91, 91, 14}, {85, 85, 85, 6}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {74, 91, 96, 45}, {74, 90, 97, 79}, {75, 89, 96, 112}, {76, 90, 98, 145}, {74, 88, 96, 176}, {76, 90, 98, 201}, {75, 90, 97, 219}, {75, 89, 98, 237}, {75, 90, 97, 253}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {74, 89, 96, 252}, {74, 89, 95, 235}, {74, 88, 95, 218}, {73, 88, 94, 200}, {73, 88, 95, 174}, {74, 88, 95, 142}, {73, 84, 94, 109}, {74, 87, 94, 76}, {73, 85, 97, 42}, {64, 96, 96, 8}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 3}, {74, 91, 96, 45}, {75, 91, 97, 92}, {75, 90, 97, 139}, {75, 89, 98, 183}, {76, 90, 97, 226}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 254}, {74, 89, 96, 223}, {85, 105, 112, 180}, {87, 104, 113, 135}, {86, 106, 112, 89}, {87, 106, 112, 41}, {128, 128, 128, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 89, 89, 20}, {76, 91, 98, 81}, {75, 88, 97, 142}, {76, 90, 97, 199}, {75, 90, 97, 247}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {79, 96, 105, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {86, 105, 114, 245}, {84, 103, 112, 195}, {84, 102, 112, 137}, {86, 106, 116, 77}, {90, 105, 120, 17}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 109, 14}, {77, 92, 98, 86}, {74, 89, 97, 158}, {75, 89, 98, 217}, {74, 89, 96, 254}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {74, 89, 96, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {86, 105, 114, 253}, {84, 104, 114, 213}, {85, 105, 113, 153}, {86, 105, 115, 80}, {77, 102, 102, 10}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 87, 97, 50}, {76, 90, 99, 124}, {75, 89, 97, 195}, {75, 90, 98, 250}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {82, 100, 108, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 104, 113, 248}, {85, 103, 113, 190}, {84, 105, 114, 119}, {87, 104, 116, 44}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 93, 52}, {75, 89, 97, 140}, {74, 89, 97, 223}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {75, 92, 99, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {85, 102, 113, 217}, {84, 105, 113, 133}, {85, 102, 113, 45}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 91, 96, 45}, {76, 89, 97, 137}, {74, 89, 97, 220}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 87, 95, 255}, {83, 102, 111, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {85, 104, 113, 213}, {82, 104, 114, 130}, {81, 101, 114, 38}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {93, 93, 116, 11}, {75, 88, 95, 102}, {75, 89, 98, 201}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {78, 95, 103, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 254}, {83, 104, 112, 193}, {85, 104, 112, 93}, {73, 109, 109, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 87, 96, 53}, {75, 90, 98, 156}, {75, 91, 97, 242}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {74, 89, 96, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 111, 238}, {83, 102, 112, 148}, {85, 102, 113, 45}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {75, 90, 96, 82}, {74, 88, 96, 196}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {80, 98, 106, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {85, 104, 112, 189}, {84, 105, 115, 73}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 96, 96, 8}, {76, 91, 98, 107}, {75, 89, 97, 218}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {74, 90, 97, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 103, 111, 211}, {84, 103, 110, 97}, {102, 102, 102, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {75, 90, 96, 122}, {76, 89, 98, 235}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {83, 100, 109, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 112, 229}, {83, 101, 110, 111}, {85, 128, 128, 6}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {73, 88, 97, 116}, {76, 90, 97, 233}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {77, 93, 102, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {83, 101, 111, 227}, {85, 102, 112, 105}, {102, 102, 102, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 128, 128, 4}, {77, 88, 97, 110}, {75, 90, 97, 229}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 95, 255}, {83, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 223}, {84, 102, 112, 100}, {128, 128, 128, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 98, 65}, {75, 90, 97, 213}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {78, 95, 104, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 206}, {83, 102, 111, 55}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {78, 89, 100, 23}, {75, 91, 97, 166}, {75, 89, 96, 254}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {74, 89, 97, 255}, {85, 103, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {83, 102, 111, 253}, {83, 101, 111, 154}, {90, 105, 120, 17}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {128, 128, 128, 2}, {76, 90, 96, 111}, {76, 91, 97, 239}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {82, 99, 108, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {83, 102, 111, 234}, {82, 103, 111, 99}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 91, 100, 56}, {75, 90, 96, 207}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {76, 92, 100, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {83, 102, 111, 197}, {78, 100, 111, 46}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 99, 18}, {75, 88, 96, 156}, {75, 90, 97, 253}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {83, 100, 110, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 112, 251}, {81, 103, 110, 144}, {85, 106, 106, 12}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 88, 95, 99}, {76, 90, 98, 235}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {78, 94, 103, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 111, 229}, {83, 101, 113, 86}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {78, 98, 98, 13}, {75, 89, 96, 170}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 96, 255}, {84, 103, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 102, 112, 155}, {96, 96, 128, 8}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 87, 97, 50}, {75, 90, 97, 219}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {80, 97, 105, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 111, 210}, {83, 102, 108, 40}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 90, 97, 108}, {76, 90, 98, 247}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {75, 91, 98, 255}, {85, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 102, 111, 243}, {83, 103, 111, 92}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 109, 14}, {75, 89, 97, 171}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {82, 100, 108, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 157}, {85, 113, 113, 9}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 93, 52}, {75, 89, 97, 221}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {77, 93, 101, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 111, 212}, {81, 100, 112, 41}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 88, 97, 110}, {75, 90, 97, 247}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 95, 255}, {83, 102, 111, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 243}, {81, 102, 113, 95}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {75, 89, 97, 171}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {80, 96, 104, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 99, 109, 157}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 102, 15}, {75, 90, 98, 196}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {74, 90, 97, 255}, {85, 103, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 111, 182}, {85, 113, 113, 9}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 92, 102, 25}, {75, 90, 97, 210}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {81, 98, 107, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {82, 101, 111, 199}, {75, 90, 105, 17}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 87, 94, 38}, {74, 89, 97, 223}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {75, 91, 99, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 100, 111, 214}, {82, 100, 109, 28}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 87, 96, 53}, {76, 89, 97, 234}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {83, 101, 110, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {81, 102, 111, 226}, {79, 103, 109, 42}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 97, 71}, {76, 90, 97, 243}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {78, 94, 103, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 237}, {79, 101, 110, 58}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 91, 99, 90}, {76, 90, 98, 248}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {82, 100, 110, 244}, {82, 102, 112, 75}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 96, 112}, {75, 90, 97, 253}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {81, 97, 106, 255}, {85, 104, 113, 255}, {86, 105, 114, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {85, 104, 113, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {84, 103, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {83, 102, 112, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {81, 100, 110, 250}, {81, 102, 113, 95}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 96, 112}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 90, 97, 255}, {90, 134, 140, 255}, {95, 160, 166, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {96, 163, 168, 255}, {95, 162, 168, 255}, {96, 163, 168, 255}, {95, 162, 168, 255}, {96, 163, 168, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {94, 162, 167, 255}, {95, 162, 168, 255}, {95, 162, 168, 255}, {95, 163, 168, 255}, {100, 169, 175, 255}, {100, 169, 175, 255}, {100, 169, 175, 255}, {100, 169, 175, 255}, {100, 169, 175, 255}, {99, 169, 175, 255}, {100, 169, 175, 255}, {100, 169, 175, 255}, {99, 169, 175, 255}, {99, 169, 175, 255}, {99, 169, 175, 255}, {100, 169, 175, 255}, {99, 169, 175, 255}, {99, 169, 175, 255}, {99, 169, 175, 255}, {99, 169, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 169, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {99, 168, 175, 255}, {98, 168, 174, 255}, {98, 168, 174, 255}, {99, 168, 175, 255}, {98, 168, 174, 255}, {98, 168, 174, 255}, {98, 168, 174, 255}, {98, 168, 174, 255}, {97, 168, 174, 255}, {98, 168, 174, 255}, {97, 168, 174, 255}, {97, 168, 174, 255}, {98, 168, 174, 255}, {97, 168, 174, 255}, {97, 168, 174, 255}, {97, 168, 174, 255}, {97, 168, 174, 255}, {97, 168, 174, 255}, {97, 168, 174, 255}, {96, 163, 170, 255}, {87, 133, 141, 255}, {78, 97, 106, 255}, {81, 100, 110, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 254}, {82, 101, 112, 93}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 91, 99, 90}, {75, 90, 97, 253}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {122, 184, 189, 255}, {143, 252, 255, 255}, {72, 237, 253, 255}, {46, 214, 232, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {82, 166, 176, 255}, {77, 95, 104, 255}, {82, 100, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {81, 100, 110, 250}, {83, 103, 110, 74}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 97, 71}, {75, 90, 97, 248}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {95, 135, 140, 255}, {161, 231, 240, 255}, {166, 255, 255, 255}, {100, 244, 255, 255}, {44, 233, 255, 255}, {52, 233, 250, 255}, {38, 205, 225, 255}, {25, 194, 216, 255}, {26, 193, 215, 255}, {25, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {89, 129, 135, 255}, {79, 97, 106, 255}, {82, 102, 111, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {82, 100, 110, 244}, {77, 100, 109, 56}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 87, 96, 53}, {76, 90, 97, 243}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 91, 98, 255}, {146, 214, 221, 255}, {149, 228, 237, 255}, {165, 255, 255, 255}, {141, 254, 255, 255}, {70, 237, 255, 255}, {28, 231, 255, 255}, {29, 232, 255, 255}, {54, 228, 245, 255}, {31, 196, 218, 255}, {25, 192, 214, 255}, {26, 193, 214, 255}, {25, 193, 215, 255}, {26, 193, 215, 255}, {25, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {64, 189, 202, 255}, {76, 94, 102, 255}, {81, 100, 109, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 99, 109, 236}, {81, 100, 112, 41}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 87, 94, 38}, {75, 89, 96, 234}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {107, 161, 166, 255}, {158, 229, 238, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {115, 247, 255, 255}, {52, 234, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {36, 233, 254, 255}, {51, 219, 236, 255}, {26, 193, 213, 255}, {25, 192, 213, 255}, {26, 193, 213, 255}, {26, 193, 214, 255}, {26, 193, 215, 255}, {26, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {93, 151, 157, 255}, {77, 96, 104, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 101, 110, 225}, {82, 100, 109, 28}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 92, 102, 25}, {75, 90, 97, 224}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {81, 103, 110, 255}, {156, 226, 233, 255}, {144, 226, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {78, 238, 255, 255}, {34, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {45, 233, 252, 255}, {45, 210, 228, 255}, {25, 191, 213, 255}, {25, 191, 213, 255}, {26, 193, 213, 255}, {25, 192, 214, 255}, {25, 193, 215, 255}, {26, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {42, 192, 210, 255}, {80, 101, 110, 255}, {80, 99, 108, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {81, 101, 109, 213}, {75, 90, 105, 17}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 102, 15}, {76, 89, 98, 211}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {121, 183, 188, 255}, {155, 228, 237, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {128, 250, 254, 255}, {60, 234, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 232, 255, 255}, {52, 230, 247, 255}, {36, 202, 221, 255}, {26, 190, 212, 255}, {26, 192, 213, 255}, {26, 193, 213, 255}, {25, 192, 214, 255}, {26, 193, 215, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {90, 171, 178, 255}, {77, 95, 103, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {80, 99, 108, 198}, {85, 113, 113, 9}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {75, 90, 98, 196}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {88, 122, 127, 255}, {160, 230, 239, 255}, {142, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {89, 241, 255, 255}, {41, 232, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {30, 233, 255, 255}, {55, 225, 241, 255}, {29, 194, 213, 255}, {27, 191, 212, 255}, {25, 191, 213, 255}, {26, 193, 213, 255}, {25, 192, 214, 255}, {26, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {22, 193, 213, 255}, {84, 116, 123, 255}, {79, 97, 107, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {80, 99, 108, 181}, {85, 170, 170, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 97, 171}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {135, 204, 210, 255}, {151, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {137, 253, 255, 255}, {65, 235, 255, 255}, {27, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {36, 233, 254, 255}, {52, 218, 234, 255}, {26, 190, 211, 255}, {25, 191, 213, 255}, {26, 192, 212, 255}, {25, 192, 214, 255}, {25, 193, 215, 255}, {26, 193, 215, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {77, 185, 195, 255}, {76, 94, 102, 255}, {81, 100, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {80, 100, 108, 153}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 88, 97, 110}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {99, 146, 151, 255}, {160, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {106, 245, 253, 255}, {47, 233, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {45, 232, 250, 255}, {44, 208, 226, 255}, {26, 190, 211, 255}, {26, 192, 213, 255}, {26, 193, 213, 255}, {26, 193, 214, 255}, {25, 193, 215, 255}, {25, 193, 215, 255}, {25, 194, 216, 255}, {26, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {89, 136, 143, 255}, {77, 96, 104, 255}, {82, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 101, 109, 91}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 95, 51}, {75, 90, 97, 247}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {78, 96, 102, 255}, {149, 219, 227, 255}, {148, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {141, 254, 254, 255}, {73, 237, 255, 255}, {30, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 232, 255, 255}, {52, 229, 246, 255}, {36, 200, 219, 255}, {26, 190, 212, 255}, {25, 191, 213, 255}, {25, 192, 213, 255}, {26, 193, 214, 255}, {25, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {56, 192, 206, 255}, {77, 97, 105, 255}, {80, 100, 108, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {80, 100, 109, 242}, {85, 105, 111, 39}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 109, 14}, {75, 89, 97, 221}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {112, 170, 174, 255}, {157, 228, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {118, 247, 253, 255}, {55, 234, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {30, 232, 254, 255}, {53, 224, 240, 255}, {29, 193, 213, 255}, {26, 190, 212, 255}, {25, 191, 212, 255}, {25, 192, 213, 255}, {25, 192, 214, 255}, {25, 193, 215, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {91, 158, 165, 255}, {78, 95, 104, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {81, 100, 110, 209}, {73, 109, 109, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 97, 171}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {84, 110, 116, 255}, {159, 228, 236, 255}, {144, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 254, 254, 255}, {82, 238, 254, 255}, {36, 232, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {37, 233, 253, 255}, {50, 215, 232, 255}, {27, 191, 211, 255}, {25, 191, 213, 255}, {25, 191, 212, 255}, {25, 192, 214, 255}, {25, 193, 215, 255}, {25, 193, 215, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {32, 193, 212, 255}, {80, 106, 114, 255}, {78, 98, 107, 255}, {81, 101, 110, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {80, 100, 108, 153}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 95, 107}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {127, 192, 197, 255}, {153, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {130, 249, 252, 255}, {61, 235, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {45, 232, 250, 255}, {43, 207, 225, 255}, {26, 190, 211, 255}, {25, 191, 213, 255}, {25, 192, 213, 255}, {25, 192, 214, 255}, {24, 193, 215, 255}, {24, 193, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {85, 176, 185, 255}, {76, 93, 101, 255}, {81, 100, 109, 255}, {82, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {80, 100, 109, 89}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 87, 97, 50}, {75, 90, 97, 247}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {93, 131, 136, 255}, {161, 230, 238, 255}, {141, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {96, 241, 253, 255}, {43, 233, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 232, 255, 255}, {52, 227, 245, 255}, {34, 199, 218, 255}, {26, 190, 212, 255}, {24, 191, 213, 255}, {25, 192, 213, 255}, {25, 192, 214, 255}, {24, 193, 215, 255}, {25, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {86, 123, 131, 255}, {78, 97, 105, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 241}, {78, 99, 106, 36}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {78, 98, 98, 13}, {76, 91, 97, 219}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 90, 98, 255}, {140, 211, 218, 255}, {149, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {138, 252, 253, 255}, {67, 236, 255, 255}, {27, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {31, 232, 254, 255}, {52, 223, 239, 255}, {28, 192, 213, 255}, {25, 190, 212, 255}, {25, 191, 212, 255}, {24, 192, 213, 255}, {24, 192, 214, 255}, {24, 193, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {67, 188, 199, 255}, {75, 93, 101, 255}, {81, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 207}, {73, 109, 109, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 96, 170}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {104, 154, 160, 255}, {158, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {110, 245, 252, 255}, {51, 233, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {39, 233, 253, 255}, {48, 214, 231, 255}, {25, 190, 211, 255}, {25, 191, 213, 255}, {24, 191, 212, 255}, {24, 192, 214, 255}, {24, 193, 215, 255}, {24, 193, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {91, 146, 152, 255}, {77, 95, 103, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 100, 110, 151}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 88, 95, 99}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {79, 100, 106, 255}, {155, 223, 231, 255}, {145, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {142, 254, 254, 255}, {76, 237, 255, 255}, {33, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {45, 231, 249, 255}, {41, 205, 224, 255}, {26, 190, 211, 255}, {24, 191, 213, 255}, {24, 192, 213, 255}, {24, 192, 214, 255}, {24, 193, 215, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {45, 192, 209, 255}, {78, 99, 107, 255}, {80, 99, 107, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {77, 100, 110, 79}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 99, 18}, {76, 90, 98, 235}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {117, 179, 183, 255}, {156, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {125, 248, 253, 255}, {57, 234, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 232, 255, 255}, {52, 228, 245, 255}, {33, 197, 217, 255}, {25, 190, 212, 255}, {23, 191, 213, 255}, {24, 192, 213, 255}, {24, 192, 214, 255}, {23, 193, 214, 255}, {24, 194, 216, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {89, 166, 172, 255}, {76, 94, 103, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 100, 109, 225}, {77, 102, 102, 10}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 88, 96, 156}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {87, 117, 122, 255}, {159, 229, 238, 255}, {143, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {86, 239, 254, 255}, {38, 232, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {30, 232, 254, 255}, {52, 221, 238, 255}, {26, 192, 211, 255}, {24, 190, 212, 255}, {24, 191, 212, 255}, {23, 192, 213, 255}, {24, 192, 214, 255}, {23, 193, 214, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {24, 193, 213, 255}, {83, 111, 119, 255}, {79, 98, 106, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 99, 108, 137}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 91, 100, 56}, {76, 90, 97, 253}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {133, 200, 207, 255}, {152, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {135, 251, 253, 255}, {64, 235, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {39, 233, 253, 255}, {47, 213, 230, 255}, {24, 190, 211, 255}, {23, 191, 213, 255}, {24, 191, 212, 255}, {23, 192, 214, 255}, {24, 193, 215, 255}, {23, 193, 214, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {79, 181, 191, 255}, {76, 94, 102, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 98, 109, 249}, {79, 103, 109, 42}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {128, 128, 128, 2}, {75, 90, 96, 207}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {96, 140, 145, 255}, {160, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {101, 243, 253, 255}, {45, 233, 255, 255}, {25, 231, 255, 255}, {26, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {47, 230, 248, 255}, {40, 203, 222, 255}, {24, 190, 211, 255}, {23, 191, 213, 255}, {23, 192, 213, 255}, {23, 192, 214, 255}, {23, 193, 214, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {88, 131, 138, 255}, {76, 95, 104, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 109, 191}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 90, 96, 111}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {76, 93, 99, 255}, {148, 216, 223, 255}, {148, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {141, 253, 254, 255}, {71, 237, 255, 255}, {29, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 232, 255, 255}, {51, 227, 244, 255}, {32, 197, 216, 255}, {24, 190, 212, 255}, {23, 191, 213, 255}, {23, 192, 213, 255}, {23, 192, 214, 255}, {22, 192, 214, 255}, {23, 194, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {58, 190, 205, 255}, {76, 95, 102, 255}, {80, 99, 108, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {80, 100, 108, 92}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {78, 89, 100, 23}, {76, 91, 97, 239}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {109, 165, 170, 255}, {157, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {117, 245, 252, 255}, {53, 234, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {30, 232, 254, 255}, {52, 222, 237, 255}, {23, 190, 211, 255}, {24, 190, 212, 255}, {23, 191, 212, 255}, {23, 192, 213, 255}, {22, 191, 213, 255}, {23, 193, 214, 255}, {22, 193, 215, 255}, {23, 194, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {91, 154, 162, 255}, {76, 95, 103, 255}, {80, 100, 109, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 231}, {73, 91, 109, 14}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 91, 97, 166}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {82, 105, 112, 255}, {157, 227, 234, 255}, {144, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {80, 238, 254, 255}, {34, 232, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {39, 232, 252, 255}, {46, 213, 230, 255}, {24, 190, 211, 255}, {23, 191, 213, 255}, {22, 191, 212, 255}, {22, 191, 213, 255}, {22, 192, 214, 255}, {22, 192, 214, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {34, 192, 211, 255}, {78, 103, 110, 255}, {78, 98, 106, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {76, 97, 108, 147}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 88, 96, 64}, {75, 89, 96, 254}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {124, 188, 193, 255}, {154, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {129, 249, 253, 255}, {60, 234, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {46, 231, 248, 255}, {37, 202, 222, 255}, {24, 190, 211, 255}, {23, 191, 213, 255}, {22, 191, 212, 255}, {23, 192, 214, 255}, {22, 192, 214, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {87, 173, 181, 255}, {75, 93, 102, 255}, {81, 101, 110, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 97, 107, 252}, {78, 99, 109, 49}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 128, 128, 4}, {77, 90, 98, 213}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {89, 126, 131, 255}, {161, 231, 239, 255}, {142, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {92, 240, 253, 255}, {41, 232, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {25, 232, 255, 255}, {50, 226, 243, 255}, {29, 195, 215, 255}, {23, 190, 212, 255}, {22, 191, 212, 255}, {22, 191, 212, 255}, {22, 191, 213, 255}, {22, 192, 214, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 191, 213, 255}, {84, 119, 125, 255}, {76, 95, 105, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {79, 99, 109, 199}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 89, 96, 109}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {140, 209, 215, 255}, {150, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {137, 252, 253, 255}, {66, 235, 255, 255}, {26, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {31, 233, 254, 255}, {51, 220, 236, 255}, {23, 190, 211, 255}, {22, 190, 212, 255}, {22, 191, 212, 255}, {22, 191, 212, 255}, {22, 191, 213, 255}, {22, 192, 214, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {73, 186, 198, 255}, {75, 92, 101, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {79, 99, 111, 90}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {76, 90, 98, 229}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {103, 150, 155, 255}, {159, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {107, 244, 253, 255}, {48, 233, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {39, 232, 252, 255}, {44, 211, 229, 255}, {23, 190, 211, 255}, {22, 191, 212, 255}, {21, 190, 211, 255}, {22, 191, 213, 255}, {22, 192, 214, 255}, {21, 192, 214, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {90, 141, 147, 255}, {76, 95, 104, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 98, 109, 216}, {85, 170, 170, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 88, 97, 116}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {78, 97, 104, 255}, {152, 221, 229, 255}, {147, 226, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {140, 253, 254, 255}, {74, 238, 255, 255}, {31, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {46, 230, 247, 255}, {34, 200, 220, 255}, {23, 190, 211, 255}, {22, 191, 212, 255}, {22, 191, 212, 255}, {21, 191, 213, 255}, {21, 192, 214, 255}, {21, 193, 215, 255}, {22, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {47, 191, 208, 255}, {76, 96, 105, 255}, {78, 98, 107, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {79, 100, 108, 97}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {76, 90, 97, 233}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {115, 174, 180, 255}, {156, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {120, 247, 253, 255}, {56, 234, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {24, 232, 255, 255}, {50, 224, 242, 255}, {27, 194, 214, 255}, {22, 189, 211, 255}, {22, 191, 212, 255}, {22, 191, 212, 255}, {21, 191, 213, 255}, {21, 192, 214, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {90, 162, 169, 255}, {75, 94, 102, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 108, 221}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 96, 122}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {86, 113, 119, 255}, {159, 227, 236, 255}, {143, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {84, 239, 254, 255}, {35, 232, 255, 255}, {24, 231, 255, 255}, {25, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {31, 232, 253, 255}, {48, 218, 234, 255}, {21, 189, 210, 255}, {21, 189, 211, 255}, {21, 190, 211, 255}, {21, 191, 213, 255}, {21, 192, 214, 255}, {21, 192, 214, 255}, {21, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {27, 193, 212, 255}, {80, 108, 116, 255}, {78, 95, 105, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {79, 99, 109, 103}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 96, 96, 8}, {75, 89, 97, 234}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {130, 197, 202, 255}, {152, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {133, 251, 253, 255}, {62, 235, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {40, 232, 251, 255}, {42, 209, 227, 255}, {22, 189, 210, 255}, {21, 190, 212, 255}, {21, 191, 212, 255}, {20, 191, 213, 255}, {21, 192, 214, 255}, {21, 192, 214, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {82, 178, 189, 255}, {75, 93, 101, 255}, {80, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {79, 98, 109, 223}, {85, 170, 170, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 91, 98, 107}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {94, 135, 140, 255}, {161, 230, 238, 255}, {141, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {98, 242, 253, 255}, {43, 233, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {46, 230, 247, 255}, {33, 199, 220, 255}, {22, 189, 210, 255}, {20, 190, 212, 255}, {21, 191, 212, 255}, {20, 191, 213, 255}, {21, 192, 214, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 193, 215, 255}, {86, 127, 134, 255}, {77, 95, 104, 255}, {80, 100, 109, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {76, 97, 108, 87}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {75, 89, 97, 218}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 91, 98, 255}, {144, 214, 220, 255}, {148, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {141, 253, 254, 255}, {68, 236, 255, 255}, {27, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {24, 232, 255, 255}, {49, 223, 241, 255}, {26, 193, 213, 255}, {21, 189, 211, 255}, {20, 190, 212, 255}, {20, 191, 212, 255}, {21, 191, 213, 255}, {21, 192, 214, 255}, {20, 193, 215, 255}, {21, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {62, 189, 202, 255}, {74, 92, 100, 255}, {79, 98, 107, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 202}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 91, 98, 81}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {107, 160, 164, 255}, {158, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {113, 245, 253, 255}, {51, 234, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {31, 232, 253, 255}, {47, 218, 235, 255}, {21, 189, 210, 255}, {21, 189, 211, 255}, {20, 190, 211, 255}, {20, 191, 213, 255}, {20, 192, 214, 255}, {20, 192, 214, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {91, 150, 156, 255}, {75, 94, 102, 255}, {80, 100, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 99, 107, 62}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 96, 196}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {80, 103, 109, 255}, {157, 226, 233, 255}, {145, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {142, 254, 254, 255}, {77, 238, 255, 255}, {32, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {39, 231, 250, 255}, {40, 208, 226, 255}, {21, 189, 210, 255}, {20, 190, 212, 255}, {20, 191, 212, 255}, {20, 191, 213, 255}, {20, 192, 214, 255}, {20, 192, 214, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {39, 191, 209, 255}, {78, 99, 108, 255}, {78, 96, 106, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {79, 99, 109, 178}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 93, 52}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {119, 183, 188, 255}, {154, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 237, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {126, 248, 253, 255}, {57, 234, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 232, 255, 255}, {46, 229, 246, 255}, {31, 199, 219, 255}, {20, 189, 211, 255}, {20, 190, 212, 255}, {20, 191, 212, 255}, {20, 191, 213, 255}, {19, 192, 214, 255}, {20, 193, 215, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {89, 170, 178, 255}, {75, 93, 101, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 253}, {80, 95, 109, 35}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 98, 156}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {88, 122, 127, 255}, {160, 230, 239, 255}, {142, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {88, 240, 254, 255}, {38, 232, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {24, 231, 254, 255}, {50, 224, 241, 255}, {24, 193, 212, 255}, {21, 189, 211, 255}, {19, 190, 212, 255}, {19, 191, 212, 255}, {20, 191, 213, 255}, {19, 192, 214, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {19, 192, 212, 255}, {82, 114, 121, 255}, {77, 96, 105, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {79, 98, 107, 136}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {93, 93, 116, 11}, {75, 90, 96, 242}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {135, 204, 210, 255}, {151, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {136, 252, 253, 255}, {63, 234, 255, 255}, {25, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {24, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {31, 232, 254, 255}, {46, 217, 233, 255}, {21, 188, 209, 255}, {20, 190, 212, 255}, {19, 190, 211, 255}, {20, 191, 213, 255}, {19, 192, 214, 255}, {19, 192, 214, 255}, {20, 193, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {75, 184, 194, 255}, {74, 92, 100, 255}, {79, 98, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {78, 98, 108, 231}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 88, 95, 102}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {99, 144, 150, 255}, {160, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {103, 244, 252, 255}, {45, 233, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {39, 231, 250, 255}, {39, 207, 225, 255}, {20, 189, 210, 255}, {19, 190, 212, 255}, {19, 191, 212, 255}, {19, 191, 213, 255}, {19, 192, 214, 255}, {19, 192, 214, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {88, 135, 143, 255}, {76, 95, 103, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {79, 98, 107, 81}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 90, 98, 201}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {78, 96, 102, 255}, {149, 219, 227, 255}, {147, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {166, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {140, 254, 254, 255}, {72, 236, 255, 255}, {28, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 231, 255, 255}, {46, 228, 245, 255}, {31, 198, 218, 255}, {20, 189, 211, 255}, {19, 190, 212, 255}, {19, 191, 212, 255}, {19, 191, 213, 255}, {19, 192, 214, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {53, 191, 206, 255}, {75, 95, 103, 255}, {78, 98, 106, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 182}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 91, 96, 45}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {112, 170, 174, 255}, {156, 228, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {117, 246, 252, 255}, {53, 234, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {23, 231, 254, 255}, {49, 222, 240, 255}, {22, 192, 212, 255}, {20, 189, 211, 255}, {19, 190, 212, 255}, {18, 190, 212, 255}, {19, 191, 213, 255}, {18, 191, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {90, 158, 164, 255}, {76, 93, 102, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 97, 106, 252}, {82, 100, 109, 28}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 90, 98, 136}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {83, 108, 115, 255}, {159, 228, 236, 255}, {144, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 254, 254, 255}, {81, 238, 255, 255}, {33, 232, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {31, 232, 253, 255}, {44, 215, 232, 255}, {20, 189, 210, 255}, {19, 190, 212, 255}, {18, 189, 211, 255}, {19, 191, 213, 255}, {19, 192, 214, 255}, {19, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {31, 192, 212, 255}, {78, 104, 113, 255}, {77, 96, 106, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 99, 108, 116}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 97, 220}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {128, 193, 197, 255}, {153, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {129, 250, 253, 255}, {60, 234, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {39, 231, 250, 255}, {37, 205, 224, 255}, {19, 189, 210, 255}, {19, 190, 212, 255}, {18, 190, 212, 255}, {18, 190, 213, 255}, {19, 192, 214, 255}, {18, 191, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {83, 175, 184, 255}, {75, 92, 100, 255}, {79, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {78, 97, 107, 202}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 95, 51}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {93, 131, 136, 255}, {161, 230, 238, 255}, {141, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {93, 240, 253, 255}, {41, 233, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 231, 255, 255}, {46, 228, 244, 255}, {28, 197, 216, 255}, {20, 189, 211, 255}, {18, 189, 212, 255}, {18, 190, 212, 255}, {18, 190, 213, 255}, {18, 191, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {84, 122, 129, 255}, {76, 95, 103, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 100, 108, 33}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 97, 140}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {76, 91, 98, 255}, {140, 211, 218, 255}, {149, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {137, 252, 253, 255}, {64, 235, 255, 255}, {25, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {25, 231, 254, 255}, {47, 222, 239, 255}, {21, 190, 212, 255}, {19, 189, 211, 255}, {18, 189, 211, 255}, {18, 190, 212, 255}, {17, 190, 213, 255}, {18, 191, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {65, 188, 199, 255}, {73, 91, 100, 255}, {79, 98, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {78, 99, 107, 121}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 97, 223}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {104, 154, 159, 255}, {158, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {108, 244, 252, 255}, {47, 233, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {32, 232, 253, 255}, {43, 214, 230, 255}, {19, 189, 210, 255}, {18, 189, 212, 255}, {18, 189, 211, 255}, {17, 190, 213, 255}, {18, 191, 214, 255}, {17, 191, 213, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {89, 145, 151, 255}, {75, 93, 102, 255}, {79, 99, 109, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {78, 97, 107, 205}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 87, 97, 50}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {79, 99, 106, 255}, {155, 223, 231, 255}, {145, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {141, 254, 254, 255}, {74, 237, 255, 255}, {29, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {40, 230, 250, 255}, {35, 203, 223, 255}, {18, 188, 210, 255}, {18, 189, 212, 255}, {18, 190, 212, 255}, {17, 190, 213, 255}, {17, 191, 213, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {43, 191, 208, 255}, {76, 97, 105, 255}, {78, 97, 105, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {82, 99, 107, 31}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 90, 99, 124}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {118, 179, 184, 255}, {155, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {122, 247, 253, 255}, {55, 234, 255, 255}, {22, 231, 255, 255}, {23, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 231, 255, 255}, {46, 228, 244, 255}, {27, 196, 216, 255}, {18, 188, 211, 255}, {18, 189, 212, 255}, {18, 190, 212, 255}, {17, 190, 213, 255}, {17, 191, 213, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {88, 164, 171, 255}, {74, 92, 101, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 105, 104}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 97, 195}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {86, 117, 122, 255}, {159, 229, 238, 255}, {143, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {84, 239, 254, 255}, {35, 232, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {24, 231, 254, 255}, {46, 221, 239, 255}, {19, 190, 210, 255}, {18, 188, 211, 255}, {17, 189, 211, 255}, {17, 190, 212, 255}, {17, 190, 213, 255}, {17, 191, 213, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {21, 192, 213, 255}, {81, 110, 117, 255}, {77, 96, 104, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {78, 98, 107, 177}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 109, 14}, {75, 90, 98, 250}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {132, 200, 206, 255}, {152, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {133, 252, 254, 255}, {62, 234, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {33, 232, 253, 255}, {41, 212, 229, 255}, {18, 188, 210, 255}, {17, 189, 212, 255}, {17, 189, 211, 255}, {17, 190, 213, 255}, {17, 191, 213, 255}, {17, 191, 213, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {77, 181, 189, 255}, {74, 92, 100, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 242}, {102, 102, 102, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 96, 85}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {96, 140, 146, 255}, {160, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {144, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {98, 243, 253, 255}, {43, 233, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {41, 229, 248, 255}, {33, 201, 221, 255}, {18, 188, 210, 255}, {17, 189, 212, 255}, {17, 190, 212, 255}, {16, 190, 212, 255}, {17, 191, 213, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {87, 130, 137, 255}, {74, 93, 103, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 108, 66}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 89, 97, 158}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {76, 93, 99, 255}, {147, 216, 223, 255}, {147, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {138, 253, 254, 255}, {68, 236, 255, 255}, {26, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 231, 255, 255}, {45, 227, 244, 255}, {24, 195, 215, 255}, {18, 188, 211, 255}, {17, 189, 212, 255}, {16, 190, 211, 255}, {16, 190, 212, 255}, {16, 191, 213, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {56, 189, 204, 255}, {74, 93, 100, 255}, {77, 96, 106, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {78, 96, 107, 138}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 98, 217}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {109, 165, 170, 255}, {158, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {114, 245, 252, 255}, {50, 233, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {24, 231, 254, 255}, {46, 221, 237, 255}, {17, 188, 210, 255}, {18, 188, 211, 255}, {17, 189, 211, 255}, {16, 190, 211, 255}, {16, 190, 212, 255}, {16, 191, 213, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {89, 153, 158, 255}, {74, 93, 101, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {78, 97, 107, 200}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 94, 94, 19}, {75, 89, 96, 254}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {81, 105, 111, 255}, {158, 228, 234, 255}, {144, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {78, 238, 254, 255}, {30, 232, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {32, 231, 252, 255}, {41, 212, 229, 255}, {17, 188, 210, 255}, {17, 189, 212, 255}, {16, 189, 210, 255}, {16, 190, 212, 255}, {16, 191, 213, 255}, {16, 191, 213, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {32, 192, 211, 255}, {77, 101, 109, 255}, {76, 96, 105, 255}, {79, 99, 108, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 96, 107, 249}, {96, 96, 128, 8}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 91, 98, 81}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {124, 188, 191, 255}, {153, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {127, 249, 253, 255}, {57, 233, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {41, 230, 249, 255}, {31, 201, 221, 255}, {17, 188, 210, 255}, {16, 189, 211, 255}, {16, 190, 211, 255}, {16, 190, 212, 255}, {15, 191, 213, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {85, 172, 179, 255}, {73, 91, 100, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {78, 99, 107, 62}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 89, 96, 141}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {90, 126, 131, 255}, {161, 231, 239, 255}, {142, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {89, 239, 252, 255}, {38, 232, 255, 255}, {22, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {18, 231, 255, 255}, {45, 225, 243, 255}, {23, 194, 214, 255}, {16, 188, 210, 255}, {16, 189, 211, 255}, {16, 190, 211, 255}, {16, 190, 212, 255}, {15, 191, 213, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 190, 213, 255}, {82, 117, 124, 255}, {75, 94, 104, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {78, 97, 107, 121}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 98, 198}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {138, 207, 215, 255}, {150, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {135, 252, 253, 255}, {64, 234, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {25, 232, 254, 255}, {45, 219, 236, 255}, {17, 188, 210, 255}, {15, 188, 210, 255}, {16, 189, 210, 255}, {16, 190, 211, 255}, {15, 190, 212, 255}, {15, 191, 213, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {69, 185, 197, 255}, {72, 90, 99, 255}, {77, 97, 106, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 96, 106, 180}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 3}, {76, 90, 98, 247}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {102, 150, 156, 255}, {159, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {105, 243, 253, 255}, {44, 232, 255, 255}, {22, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {32, 231, 252, 255}, {39, 210, 228, 255}, {17, 188, 210, 255}, {15, 189, 211, 255}, {15, 189, 210, 255}, {15, 190, 212, 255}, {15, 191, 213, 255}, {15, 191, 213, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {87, 139, 145, 255}, {74, 93, 102, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 234}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 91, 96, 45}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {78, 97, 103, 255}, {151, 221, 228, 255}, {146, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 226, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {138, 253, 254, 255}, {72, 238, 255, 255}, {27, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {41, 230, 248, 255}, {29, 199, 219, 255}, {17, 188, 210, 255}, {15, 189, 211, 255}, {15, 190, 211, 255}, {15, 190, 212, 255}, {15, 191, 213, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {45, 190, 207, 255}, {74, 94, 103, 255}, {75, 95, 105, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {71, 92, 102, 25}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 91, 97, 92}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {115, 173, 179, 255}, {156, 228, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {119, 247, 253, 255}, {53, 233, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {17, 231, 255, 255}, {45, 225, 241, 255}, {21, 192, 213, 255}, {16, 188, 210, 255}, {15, 189, 211, 255}, {15, 190, 211, 255}, {15, 190, 212, 255}, {15, 191, 213, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {88, 161, 168, 255}, {73, 92, 100, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 98, 105, 73}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 89, 96, 138}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {85, 112, 118, 255}, {159, 228, 236, 255}, {143, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 226, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {81, 240, 254, 255}, {32, 232, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {24, 231, 253, 255}, {44, 218, 236, 255}, {15, 188, 209, 255}, {14, 187, 210, 255}, {15, 189, 210, 255}, {15, 190, 212, 255}, {14, 190, 213, 255}, {15, 191, 213, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {24, 192, 212, 255}, {78, 106, 114, 255}, {75, 93, 104, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {78, 97, 108, 118}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 98, 183}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {129, 196, 200, 255}, {152, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 236, 255}, {165, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {130, 250, 253, 255}, {59, 234, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {33, 231, 251, 255}, {36, 207, 226, 255}, {15, 187, 209, 255}, {15, 189, 211, 255}, {14, 188, 210, 255}, {15, 190, 212, 255}, {14, 190, 213, 255}, {14, 190, 213, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {80, 177, 188, 255}, {73, 91, 100, 255}, {77, 96, 106, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 98, 107, 164}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 89, 96, 226}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {94, 135, 140, 255}, {161, 230, 238, 255}, {141, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {95, 242, 253, 255}, {40, 232, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {41, 229, 248, 255}, {27, 198, 219, 255}, {15, 187, 209, 255}, {15, 189, 211, 255}, {14, 189, 211, 255}, {14, 189, 212, 255}, {14, 190, 213, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 192, 215, 255}, {84, 125, 132, 255}, {74, 93, 102, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {77, 96, 106, 209}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {76, 92, 99, 255}, {144, 214, 220, 255}, {148, 227, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 226, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {139, 253, 254, 255}, {65, 235, 255, 255}, {23, 231, 255, 255}, {21, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {17, 231, 255, 255}, {43, 222, 240, 255}, {19, 191, 212, 255}, {15, 187, 210, 255}, {14, 188, 211, 255}, {14, 189, 211, 255}, {14, 189, 212, 255}, {14, 190, 213, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {61, 188, 201, 255}, {72, 90, 99, 255}, {76, 96, 105, 255}, {77, 97, 107, 255}, {78, 98, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 97, 106, 247}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 91, 96, 45}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {106, 159, 164, 255}, {158, 229, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {111, 245, 253, 255}, {47, 233, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {24, 231, 253, 255}, {43, 216, 235, 255}, {14, 187, 209, 255}, {14, 187, 210, 255}, {14, 188, 210, 255}, {13, 189, 212, 255}, {14, 190, 213, 255}, {13, 190, 213, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {88, 148, 153, 255}, {72, 92, 100, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {71, 92, 102, 25}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 92, 98, 78}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {80, 102, 108, 255}, {156, 226, 233, 255}, {144, 226, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 254, 254, 255}, {74, 238, 254, 255}, {29, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {33, 230, 250, 255}, {34, 206, 225, 255}, {15, 187, 209, 255}, {14, 188, 211, 255}, {14, 189, 211, 255}, {13, 189, 212, 255}, {14, 190, 213, 255}, {13, 190, 213, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {37, 191, 209, 255}, {75, 97, 106, 255}, {75, 94, 104, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {78, 99, 108, 59}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 96, 112}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {119, 183, 188, 255}, {154, 228, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {124, 248, 253, 255}, {55, 233, 255, 255}, {20, 231, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 231, 255, 255}, {41, 228, 247, 255}, {25, 198, 217, 255}, {15, 187, 210, 255}, {13, 188, 211, 255}, {14, 189, 211, 255}, {13, 189, 212, 255}, {14, 190, 213, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {86, 168, 175, 255}, {72, 91, 99, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {78, 97, 108, 92}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 89, 96, 144}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {88, 120, 126, 255}, {160, 230, 239, 255}, {141, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {86, 240, 254, 255}, {34, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {18, 231, 254, 255}, {45, 223, 241, 255}, {17, 191, 211, 255}, {14, 187, 210, 255}, {13, 188, 211, 255}, {14, 189, 211, 255}, {13, 189, 212, 255}, {13, 190, 213, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {17, 191, 213, 255}, {80, 113, 119, 255}, {74, 93, 103, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {78, 99, 107, 124}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 96, 176}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {135, 204, 210, 255}, {151, 227, 237, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {134, 252, 253, 255}, {61, 234, 255, 255}, {21, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {26, 232, 254, 255}, {40, 216, 233, 255}, {14, 186, 208, 255}, {13, 188, 211, 255}, {13, 188, 210, 255}, {13, 189, 212, 255}, {13, 190, 213, 255}, {13, 190, 213, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {72, 181, 193, 255}, {72, 90, 98, 255}, {76, 96, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 157}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 90, 98, 201}, {74, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {75, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {98, 144, 150, 255}, {160, 229, 238, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {100, 243, 253, 255}, {43, 232, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {34, 232, 251, 255}, {32, 205, 224, 255}, {14, 187, 209, 255}, {13, 188, 211, 255}, {12, 189, 211, 255}, {13, 189, 212, 255}, {12, 190, 213, 255}, {13, 190, 213, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {86, 134, 140, 255}, {73, 92, 100, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 96, 106, 183}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 97, 219}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {77, 95, 101, 255}, {149, 219, 227, 255}, {147, 226, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {138, 254, 254, 255}, {68, 236, 255, 255}, {25, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 231, 255, 255}, {40, 228, 246, 255}, {24, 197, 217, 255}, {13, 187, 210, 255}, {12, 188, 211, 255}, {13, 189, 211, 255}, {12, 189, 212, 255}, {12, 190, 213, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {51, 191, 206, 255}, {72, 93, 101, 255}, {76, 96, 105, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {77, 96, 107, 201}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 89, 98, 237}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {111, 169, 174, 255}, {156, 228, 238, 255}, {140, 225, 236, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {116, 246, 253, 255}, {49, 233, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {17, 231, 254, 255}, {43, 221, 239, 255}, {16, 190, 211, 255}, {13, 187, 210, 255}, {12, 188, 211, 255}, {13, 189, 211, 255}, {12, 189, 212, 255}, {12, 190, 213, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {88, 156, 163, 255}, {73, 90, 99, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 96, 105, 220}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {75, 90, 97, 253}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {82, 108, 114, 255}, {158, 228, 235, 255}, {144, 226, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 254, 254, 255}, {78, 238, 255, 255}, {31, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {25, 232, 253, 255}, {38, 214, 231, 255}, {14, 187, 209, 255}, {12, 188, 211, 255}, {12, 188, 210, 255}, {12, 189, 212, 255}, {12, 190, 213, 255}, {12, 190, 213, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {29, 191, 211, 255}, {76, 103, 111, 255}, {74, 94, 103, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 97, 106, 239}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 6}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {126, 191, 197, 255}, {152, 227, 237, 255}, {139, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {129, 250, 253, 255}, {57, 234, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {20, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {33, 231, 250, 255}, {31, 204, 223, 255}, {13, 187, 209, 255}, {12, 188, 211, 255}, {12, 189, 211, 255}, {12, 189, 212, 255}, {12, 190, 213, 255}, {11, 189, 212, 255}, {12, 191, 214, 255}, {81, 173, 183, 255}, {72, 90, 98, 255}, {77, 96, 105, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 254}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 109, 14}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {92, 130, 135, 255}, {160, 230, 238, 255}, {140, 225, 235, 255}, {140, 225, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {143, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {91, 240, 253, 255}, {38, 232, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 231, 255, 255}, {40, 228, 245, 255}, {22, 195, 215, 255}, {13, 187, 210, 255}, {12, 188, 211, 255}, {12, 189, 211, 255}, {11, 188, 211, 255}, {12, 190, 213, 255}, {12, 190, 213, 255}, {81, 120, 126, 255}, {74, 93, 102, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {64, 96, 96, 8}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 99, 18}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 90, 97, 255}, {139, 211, 217, 255}, {149, 226, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {135, 252, 253, 255}, {61, 235, 255, 255}, {21, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {17, 231, 254, 255}, {42, 222, 238, 255}, {14, 188, 210, 255}, {12, 187, 210, 255}, {12, 188, 210, 255}, {12, 189, 211, 255}, {11, 188, 211, 255}, {63, 187, 198, 255}, {71, 89, 98, 255}, {77, 96, 105, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {70, 93, 93, 11}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 97, 109, 21}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {103, 154, 158, 255}, {158, 229, 238, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {106, 244, 252, 255}, {44, 232, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {27, 232, 253, 255}, {38, 214, 231, 255}, {12, 187, 209, 255}, {12, 188, 211, 255}, {11, 188, 210, 255}, {12, 189, 212, 255}, {87, 144, 148, 255}, {73, 91, 100, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {59, 78, 98, 13}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 81, 93, 22}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {78, 99, 105, 255}, {154, 223, 231, 255}, {145, 226, 236, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {139, 254, 254, 255}, {71, 237, 255, 255}, {26, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {11, 230, 255, 255}, {34, 230, 250, 255}, {29, 202, 223, 255}, {12, 187, 209, 255}, {11, 188, 210, 255}, {41, 190, 206, 255}, {74, 95, 103, 255}, {76, 95, 103, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {64, 80, 96, 16}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {82, 92, 102, 25}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {115, 177, 183, 255}, {155, 228, 237, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {120, 247, 253, 255}, {52, 233, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {11, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {11, 230, 255, 255}, {11, 230, 255, 255}, {11, 231, 255, 255}, {41, 227, 245, 255}, {20, 194, 215, 255}, {12, 187, 210, 255}, {86, 163, 170, 255}, {72, 90, 99, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {57, 85, 85, 18}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 100, 28}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {83, 112, 119, 255}, {158, 229, 238, 255}, {142, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {139, 255, 255, 255}, {82, 239, 254, 255}, {32, 231, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {19, 230, 255, 255}, {18, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {18, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {17, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {16, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {15, 230, 255, 255}, {14, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {14, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {13, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {11, 230, 255, 255}, {12, 230, 255, 255}, {12, 230, 255, 255}, {11, 230, 255, 255}, {12, 230, 255, 255}, {17, 231, 254, 255}, {41, 221, 238, 255}, {22, 189, 208, 255}, {76, 104, 112, 255}, {75, 94, 102, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {64, 77, 89, 20}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 85, 94, 30}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {110, 159, 165, 255}, {151, 227, 237, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {131, 251, 253, 255}, {67, 236, 254, 255}, {45, 233, 253, 255}, {44, 233, 253, 255}, {44, 232, 252, 255}, {44, 232, 252, 255}, {44, 231, 251, 255}, {44, 231, 251, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {43, 231, 252, 255}, {44, 231, 252, 255}, {44, 231, 252, 255}, {43, 231, 252, 255}, {44, 231, 252, 255}, {43, 231, 252, 255}, {43, 231, 252, 255}, {43, 231, 252, 255}, {42, 231, 252, 255}, {43, 231, 252, 255}, {42, 231, 252, 255}, {43, 231, 252, 255}, {42, 231, 252, 255}, {43, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {42, 231, 252, 255}, {41, 231, 252, 255}, {42, 231, 252, 255}, {41, 231, 252, 255}, {42, 231, 252, 255}, {41, 231, 252, 255}, {42, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {40, 231, 252, 255}, {41, 231, 252, 255}, {41, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {39, 231, 252, 255}, {40, 231, 252, 255}, {40, 231, 252, 255}, {39, 231, 252, 255}, {40, 231, 252, 255}, {39, 231, 252, 255}, {39, 231, 252, 255}, {39, 231, 252, 255}, {38, 231, 252, 255}, {39, 231, 252, 255}, {38, 231, 252, 255}, {39, 231, 252, 255}, {38, 231, 252, 255}, {39, 231, 252, 255}, {38, 231, 252, 255}, {38, 231, 252, 255}, {38, 231, 252, 255}, {38, 231, 252, 255}, {38, 231, 251, 255}, {38, 231, 251, 255}, {38, 231, 251, 255}, {38, 232, 252, 255}, {37, 232, 252, 255}, {38, 232, 252, 255}, {37, 232, 252, 255}, {37, 232, 252, 255}, {38, 232, 253, 255}, {39, 233, 253, 255}, {38, 232, 253, 255}, {39, 233, 254, 255}, {39, 233, 254, 255}, {49, 234, 252, 255}, {80, 144, 151, 255}, {72, 91, 100, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {58, 81, 93, 22}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 85, 94, 30}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {74, 89, 96, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {109, 158, 162, 255}, {142, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {117, 234, 241, 255}, {55, 202, 221, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {30, 194, 212, 255}, {77, 133, 140, 255}, {72, 91, 98, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {58, 81, 93, 22}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {76, 94, 94, 27}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {82, 109, 115, 255}, {151, 226, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {139, 253, 253, 255}, {80, 211, 227, 255}, {21, 193, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {20, 193, 214, 255}, {44, 179, 193, 255}, {30, 140, 152, 255}, {71, 96, 104, 255}, {71, 89, 99, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {67, 81, 94, 19}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {82, 92, 102, 25}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {113, 173, 177, 255}, {144, 225, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {100, 226, 235, 255}, {43, 199, 218, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {13, 192, 214, 255}, {41, 187, 202, 255}, {25, 142, 154, 255}, {17, 133, 146, 255}, {85, 149, 156, 255}, {68, 84, 94, 255}, {70, 89, 98, 255}, {74, 93, 102, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {57, 85, 85, 18}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 81, 93, 22}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {77, 96, 102, 255}, {148, 221, 228, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {133, 248, 250, 255}, {73, 206, 224, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {37, 194, 210, 255}, {33, 152, 166, 255}, {19, 133, 146, 255}, {17, 136, 149, 255}, {48, 156, 166, 255}, {68, 87, 95, 255}, {69, 88, 96, 255}, {71, 90, 99, 255}, {72, 91, 100, 255}, {76, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {68, 85, 102, 15}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 89, 89, 20}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {100, 148, 153, 255}, {147, 226, 236, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {91, 217, 230, 255}, {32, 196, 217, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {29, 194, 214, 255}, {41, 168, 182, 255}, {19, 131, 146, 255}, {17, 134, 148, 255}, {17, 138, 150, 255}, {15, 139, 154, 255}, {84, 133, 140, 255}, {68, 86, 95, 255}, {71, 89, 99, 255}, {71, 90, 99, 255}, {71, 90, 98, 255}, {74, 93, 102, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {59, 78, 98, 13}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 99, 18}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {136, 207, 214, 255}, {140, 225, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {123, 240, 243, 255}, {60, 203, 222, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {19, 193, 214, 255}, {45, 180, 195, 255}, {19, 134, 145, 255}, {17, 134, 148, 255}, {17, 137, 152, 255}, {16, 141, 153, 255}, {16, 141, 155, 255}, {67, 163, 171, 255}, {65, 83, 92, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {76, 95, 105, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {70, 93, 93, 11}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 91, 91, 14}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {88, 123, 128, 255}, {151, 228, 238, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {138, 254, 254, 255}, {83, 213, 227, 255}, {23, 193, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {13, 192, 214, 255}, {44, 189, 205, 255}, {26, 143, 156, 255}, {19, 134, 146, 255}, {18, 136, 149, 255}, {17, 139, 153, 255}, {15, 141, 153, 255}, {14, 143, 157, 255}, {16, 145, 160, 255}, {78, 111, 118, 255}, {68, 86, 95, 255}, {72, 90, 99, 255}, {71, 90, 99, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {72, 91, 100, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {73, 73, 109, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 6}, {73, 88, 95, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {121, 186, 191, 255}, {142, 225, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {109, 230, 237, 255}, {50, 200, 220, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {38, 194, 211, 255}, {34, 156, 169, 255}, {19, 133, 145, 255}, {17, 135, 148, 255}, {16, 138, 152, 255}, {15, 140, 153, 255}, {14, 143, 156, 255}, {14, 143, 157, 255}, {13, 145, 159, 255}, {82, 160, 166, 255}, {66, 83, 92, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {74, 94, 103, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 254}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {74, 88, 95, 252}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {79, 103, 110, 255}, {151, 224, 233, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {136, 252, 253, 255}, {78, 209, 224, 255}, {20, 193, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {29, 194, 214, 255}, {41, 169, 181, 255}, {19, 132, 145, 255}, {18, 134, 148, 255}, {18, 138, 151, 255}, {16, 139, 153, 255}, {16, 141, 155, 255}, {14, 143, 157, 255}, {14, 144, 159, 255}, {14, 145, 161, 255}, {33, 156, 168, 255}, {72, 94, 102, 255}, {69, 88, 96, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {72, 90, 100, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {76, 96, 106, 238}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 89, 95, 235}, {73, 87, 94, 255}, {73, 88, 95, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {107, 163, 168, 255}, {145, 225, 236, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {98, 221, 232, 255}, {37, 197, 217, 255}, {18, 192, 215, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {11, 190, 213, 255}, {12, 191, 214, 255}, {19, 194, 214, 255}, {45, 181, 195, 255}, {19, 135, 147, 255}, {18, 133, 147, 255}, {18, 137, 151, 255}, {16, 140, 153, 255}, {15, 141, 155, 255}, {15, 141, 157, 255}, {15, 144, 158, 255}, {14, 145, 160, 255}, {13, 145, 160, 255}, {12, 148, 162, 255}, {86, 145, 151, 255}, {66, 83, 93, 255}, {70, 88, 98, 255}, {72, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {73, 92, 103, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 94, 105, 219}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 95, 218}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {74, 92, 98, 255}, {144, 214, 221, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {130, 245, 247, 255}, {68, 205, 222, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 193, 214, 255}, {43, 189, 205, 255}, {29, 144, 159, 255}, {19, 134, 146, 255}, {18, 136, 149, 255}, {16, 139, 152, 255}, {15, 141, 153, 255}, {14, 142, 157, 255}, {14, 144, 157, 255}, {14, 144, 159, 255}, {14, 145, 161, 255}, {12, 147, 161, 255}, {12, 148, 163, 255}, {55, 165, 175, 255}, {67, 84, 93, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 90, 100, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 97, 106, 200}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 88, 95, 199}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {94, 138, 142, 255}, {149, 226, 236, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 225, 236, 255}, {164, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {87, 215, 228, 255}, {28, 194, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {36, 194, 211, 255}, {36, 157, 172, 255}, {20, 133, 146, 255}, {17, 135, 148, 255}, {17, 138, 152, 255}, {16, 140, 153, 255}, {14, 142, 155, 255}, {14, 143, 157, 255}, {14, 144, 159, 255}, {15, 146, 161, 255}, {14, 147, 162, 255}, {13, 147, 163, 255}, {13, 148, 163, 255}, {13, 149, 165, 255}, {82, 123, 130, 255}, {68, 85, 95, 255}, {70, 89, 98, 255}, {72, 90, 99, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 90, 98, 255}, {71, 91, 100, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {76, 97, 107, 181}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 88, 95, 174}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {129, 199, 204, 255}, {140, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {118, 236, 241, 255}, {56, 202, 221, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {27, 194, 214, 255}, {42, 171, 185, 255}, {20, 132, 145, 255}, {18, 133, 149, 255}, {18, 136, 151, 255}, {16, 139, 153, 255}, {15, 141, 155, 255}, {15, 143, 157, 255}, {14, 144, 158, 255}, {14, 146, 160, 255}, {13, 145, 161, 255}, {13, 148, 163, 255}, {13, 148, 163, 255}, {12, 149, 165, 255}, {13, 150, 165, 255}, {75, 166, 173, 255}, {67, 83, 92, 255}, {70, 87, 97, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 98, 255}, {70, 90, 98, 255}, {74, 93, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {76, 94, 104, 155}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 88, 95, 142}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {83, 114, 119, 255}, {149, 227, 236, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 253, 254, 255}, {81, 212, 228, 255}, {21, 193, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {18, 193, 214, 255}, {46, 183, 196, 255}, {22, 136, 149, 255}, {19, 133, 148, 255}, {18, 135, 150, 255}, {16, 139, 152, 255}, {17, 141, 155, 255}, {15, 141, 156, 255}, {15, 144, 158, 255}, {14, 144, 160, 255}, {14, 145, 161, 255}, {13, 147, 163, 255}, {12, 147, 163, 255}, {13, 148, 164, 255}, {13, 149, 165, 255}, {13, 150, 165, 255}, {22, 155, 169, 255}, {73, 102, 109, 255}, {68, 85, 95, 255}, {72, 90, 99, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {72, 91, 100, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 96, 107, 122}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 87, 96, 109}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {114, 176, 181, 255}, {142, 225, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {104, 226, 235, 255}, {44, 199, 219, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 193, 214, 255}, {43, 189, 205, 255}, {31, 147, 160, 255}, {20, 134, 147, 255}, {18, 135, 149, 255}, {17, 138, 152, 255}, {17, 140, 154, 255}, {14, 142, 156, 255}, {15, 143, 158, 255}, {13, 144, 159, 255}, {15, 146, 161, 255}, {13, 147, 162, 255}, {13, 148, 163, 255}, {13, 148, 164, 255}, {12, 149, 165, 255}, {12, 150, 165, 255}, {12, 150, 165, 255}, {12, 151, 166, 255}, {85, 156, 161, 255}, {68, 85, 94, 255}, {72, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {71, 89, 99, 255}, {70, 90, 99, 255}, {73, 92, 103, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 97, 106, 89}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 87, 94, 76}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {76, 96, 104, 255}, {148, 222, 230, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {164, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {142, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {136, 250, 252, 255}, {74, 208, 224, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {35, 194, 211, 255}, {36, 158, 172, 255}, {20, 133, 145, 255}, {19, 134, 149, 255}, {17, 137, 151, 255}, {16, 139, 153, 255}, {16, 142, 155, 255}, {14, 143, 157, 255}, {14, 145, 159, 255}, {13, 145, 160, 255}, {13, 145, 161, 255}, {14, 147, 163, 255}, {13, 148, 163, 255}, {13, 149, 165, 255}, {12, 150, 165, 255}, {12, 150, 166, 255}, {12, 151, 166, 255}, {11, 151, 166, 255}, {39, 162, 175, 255}, {69, 89, 96, 255}, {70, 87, 96, 255}, {70, 88, 98, 255}, {72, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 90, 99, 255}, {70, 91, 99, 255}, {75, 94, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {73, 96, 105, 56}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 85, 97, 42}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {101, 152, 158, 255}, {146, 226, 236, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {93, 218, 230, 255}, {33, 197, 217, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {25, 194, 214, 255}, {44, 174, 187, 255}, {22, 132, 144, 255}, {19, 134, 148, 255}, {18, 136, 151, 255}, {17, 139, 153, 255}, {17, 141, 154, 255}, {16, 142, 156, 255}, {15, 144, 157, 255}, {14, 144, 159, 255}, {14, 145, 161, 255}, {14, 147, 163, 255}, {14, 149, 163, 255}, {14, 150, 164, 255}, {12, 149, 165, 255}, {12, 150, 165, 255}, {12, 150, 166, 255}, {11, 150, 166, 255}, {12, 152, 166, 255}, {11, 151, 167, 255}, {85, 138, 144, 255}, {68, 84, 94, 255}, {71, 89, 99, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {70, 90, 98, 255}, {71, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {73, 91, 101, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {78, 100, 111, 23}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 96, 96, 8}, {73, 87, 94, 254}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 87, 94, 255}, {138, 211, 217, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {126, 241, 245, 255}, {63, 203, 222, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {18, 194, 215, 255}, {45, 184, 197, 255}, {24, 138, 149, 255}, {20, 133, 147, 255}, {18, 135, 150, 255}, {18, 138, 152, 255}, {17, 140, 154, 255}, {15, 141, 156, 255}, {15, 143, 157, 255}, {14, 144, 160, 255}, {15, 146, 161, 255}, {14, 148, 162, 255}, {14, 149, 163, 255}, {13, 149, 164, 255}, {13, 149, 165, 255}, {12, 150, 165, 255}, {12, 150, 166, 255}, {12, 151, 166, 255}, {12, 151, 166, 255}, {11, 151, 166, 255}, {11, 153, 167, 255}, {63, 167, 176, 255}, {66, 83, 92, 255}, {70, 87, 97, 255}, {72, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {74, 94, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 96, 105, 245}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 88, 95, 223}, {72, 86, 94, 255}, {72, 86, 94, 255}, {73, 87, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {89, 127, 133, 255}, {149, 227, 237, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {85, 213, 228, 255}, {25, 194, 216, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 192, 214, 255}, {43, 191, 206, 255}, {31, 146, 160, 255}, {20, 133, 146, 255}, {19, 135, 149, 255}, {18, 138, 151, 255}, {17, 140, 153, 255}, {15, 142, 155, 255}, {15, 143, 157, 255}, {15, 145, 159, 255}, {14, 146, 160, 255}, {14, 146, 162, 255}, {14, 149, 163, 255}, {13, 148, 163, 255}, {14, 150, 165, 255}, {12, 149, 165, 255}, {12, 150, 165, 255}, {13, 152, 167, 255}, {12, 151, 167, 255}, {11, 151, 166, 255}, {10, 152, 167, 255}, {10, 153, 167, 255}, {12, 153, 168, 255}, {79, 116, 121, 255}, {69, 86, 94, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {70, 90, 98, 255}, {70, 89, 99, 255}, {71, 90, 100, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {70, 89, 98, 255}, {71, 91, 100, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {76, 95, 105, 206}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 86, 95, 180}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {122, 189, 195, 255}, {141, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {113, 233, 240, 255}, {50, 201, 220, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {35, 195, 213, 255}, {39, 161, 175, 255}, {20, 132, 145, 255}, {19, 135, 148, 255}, {18, 137, 150, 255}, {17, 139, 153, 255}, {15, 142, 154, 255}, {16, 142, 156, 255}, {14, 144, 158, 255}, {15, 145, 160, 255}, {14, 146, 160, 255}, {14, 147, 163, 255}, {14, 149, 163, 255}, {14, 150, 164, 255}, {13, 149, 166, 255}, {12, 150, 166, 255}, {13, 151, 166, 255}, {12, 151, 166, 255}, {11, 151, 166, 255}, {12, 152, 167, 255}, {11, 152, 167, 255}, {11, 152, 168, 255}, {11, 152, 168, 255}, {81, 166, 171, 255}, {66, 83, 91, 255}, {70, 88, 97, 255}, {72, 90, 99, 255}, {70, 89, 98, 255}, {72, 90, 99, 255}, {70, 89, 98, 255}, {71, 90, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {71, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 97, 255}, {71, 90, 99, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {71, 89, 99, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {74, 94, 103, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {77, 96, 105, 160}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 88, 95, 134}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {80, 106, 112, 255}, {149, 225, 232, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {137, 253, 253, 255}, {79, 210, 225, 255}, {19, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {12, 191, 214, 255}, {12, 191, 214, 255}, {25, 194, 213, 255}, {45, 175, 188, 255}, {20, 131, 143, 255}, {20, 134, 147, 255}, {19, 136, 151, 255}, {18, 140, 153, 255}, {17, 140, 155, 255}, {15, 142, 155, 255}, {15, 143, 158, 255}, {15, 145, 159, 255}, {15, 146, 161, 255}, {15, 147, 162, 255}, {14, 149, 163, 255}, {14, 149, 164, 255}, {13, 149, 165, 255}, {12, 150, 166, 255}, {13, 151, 166, 255}, {11, 151, 166, 255}, {12, 151, 166, 255}, {12, 152, 167, 255}, {12, 152, 167, 255}, {11, 152, 167, 255}, {11, 153, 168, 255}, {11, 153, 168, 255}, {28, 161, 174, 255}, {71, 95, 104, 255}, {70, 87, 96, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 90, 97, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {70, 89, 98, 255}, {70, 90, 99, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {72, 91, 99, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 106, 115}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 89, 97, 89}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {110, 168, 172, 255}, {143, 225, 236, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {99, 223, 234, 255}, {40, 198, 217, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {17, 193, 215, 255}, {46, 184, 199, 255}, {24, 139, 149, 255}, {21, 134, 147, 255}, {19, 136, 150, 255}, {17, 138, 151, 255}, {17, 140, 154, 255}, {15, 141, 156, 255}, {15, 143, 157, 255}, {15, 145, 158, 255}, {15, 146, 160, 255}, {15, 147, 162, 255}, {14, 148, 162, 255}, {14, 149, 163, 255}, {13, 149, 164, 255}, {12, 149, 165, 255}, {12, 151, 166, 255}, {12, 151, 166, 255}, {12, 151, 166, 255}, {12, 152, 168, 255}, {12, 152, 168, 255}, {11, 153, 167, 255}, {11, 153, 168, 255}, {11, 153, 168, 255}, {11, 154, 169, 255}, {11, 153, 168, 255}, {85, 149, 156, 255}, {68, 84, 93, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {73, 93, 102, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 92, 103, 69}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 87, 93, 41}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {74, 92, 99, 255}, {144, 218, 224, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {132, 247, 249, 255}, {70, 207, 223, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 192, 215, 255}, {43, 192, 207, 255}, {32, 148, 162, 255}, {21, 132, 144, 255}, {19, 135, 149, 255}, {17, 137, 151, 255}, {18, 141, 154, 255}, {16, 143, 156, 255}, {16, 142, 157, 255}, {16, 145, 160, 255}, {15, 146, 160, 255}, {15, 147, 162, 255}, {14, 148, 163, 255}, {13, 148, 163, 255}, {13, 148, 163, 255}, {12, 149, 165, 255}, {14, 151, 165, 255}, {13, 151, 166, 255}, {12, 151, 166, 255}, {12, 153, 167, 255}, {12, 151, 167, 255}, {11, 152, 167, 255}, {11, 153, 168, 255}, {11, 153, 168, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {10, 155, 169, 255}, {51, 166, 178, 255}, {68, 86, 94, 255}, {70, 87, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {71, 90, 99, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 97, 255}, {69, 89, 99, 255}, {70, 90, 99, 255}, {73, 94, 103, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {70, 93, 104, 22}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {128, 128, 128, 2}, {72, 87, 94, 245}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {96, 142, 147, 255}, {147, 226, 236, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {89, 216, 230, 255}, {29, 195, 216, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {33, 195, 212, 255}, {38, 162, 174, 255}, {21, 132, 145, 255}, {20, 135, 147, 255}, {18, 136, 150, 255}, {18, 139, 154, 255}, {17, 142, 155, 255}, {16, 142, 155, 255}, {15, 144, 158, 255}, {15, 145, 159, 255}, {14, 146, 160, 255}, {15, 147, 162, 255}, {14, 149, 163, 255}, {13, 148, 163, 255}, {14, 150, 165, 255}, {13, 150, 165, 255}, {13, 151, 165, 255}, {11, 151, 166, 255}, {11, 151, 166, 255}, {13, 153, 168, 255}, {12, 152, 168, 255}, {11, 152, 167, 255}, {12, 153, 169, 255}, {11, 153, 168, 255}, {11, 154, 169, 255}, {10, 154, 169, 255}, {11, 153, 169, 255}, {10, 154, 169, 255}, {82, 128, 134, 255}, {67, 85, 93, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {71, 90, 98, 255}, {70, 90, 99, 255}, {72, 92, 100, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {76, 96, 106, 231}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 88, 95, 195}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {133, 202, 208, 255}, {139, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {121, 239, 243, 255}, {58, 202, 222, 255}, {18, 192, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {23, 194, 214, 255}, {46, 176, 190, 255}, {21, 131, 144, 255}, {20, 133, 147, 255}, {20, 137, 151, 255}, {17, 139, 152, 255}, {17, 140, 155, 255}, {15, 142, 155, 255}, {17, 143, 158, 255}, {15, 145, 158, 255}, {15, 145, 160, 255}, {15, 147, 162, 255}, {14, 148, 162, 255}, {14, 149, 163, 255}, {14, 150, 164, 255}, {13, 151, 165, 255}, {13, 151, 166, 255}, {12, 150, 166, 255}, {12, 151, 166, 255}, {12, 152, 167, 255}, {13, 153, 169, 255}, {11, 153, 167, 255}, {11, 153, 168, 255}, {11, 152, 168, 255}, {11, 154, 169, 255}, {11, 153, 168, 255}, {10, 154, 169, 255}, {11, 155, 169, 255}, {11, 155, 169, 255}, {72, 169, 177, 255}, {65, 83, 91, 255}, {69, 88, 97, 255}, {71, 89, 99, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 90, 98, 255}, {71, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {73, 94, 103, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {76, 96, 106, 175}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 87, 95, 137}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {85, 118, 123, 255}, {149, 227, 237, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {138, 254, 254, 255}, {83, 213, 228, 255}, {22, 193, 215, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {16, 192, 214, 255}, {46, 185, 201, 255}, {23, 138, 150, 255}, {22, 133, 146, 255}, {20, 136, 149, 255}, {19, 138, 151, 255}, {18, 141, 154, 255}, {16, 141, 155, 255}, {17, 143, 158, 255}, {15, 144, 159, 255}, {15, 145, 160, 255}, {16, 147, 162, 255}, {14, 148, 162, 255}, {14, 149, 163, 255}, {14, 149, 164, 255}, {14, 150, 165, 255}, {13, 151, 166, 255}, {13, 152, 166, 255}, {11, 150, 166, 255}, {13, 153, 167, 255}, {12, 153, 168, 255}, {12, 152, 167, 255}, {11, 152, 167, 255}, {11, 153, 168, 255}, {12, 153, 170, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {11, 154, 170, 255}, {11, 154, 169, 255}, {11, 155, 169, 255}, {16, 157, 171, 255}, {76, 107, 114, 255}, {69, 87, 96, 255}, {72, 90, 99, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 90, 97, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {71, 90, 99, 255}, {70, 90, 100, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {76, 96, 107, 117}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 89, 96, 77}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {117, 181, 186, 255}, {142, 225, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {107, 228, 238, 255}, {45, 199, 219, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 192, 215, 255}, {41, 191, 208, 255}, {32, 148, 162, 255}, {21, 132, 144, 255}, {20, 136, 148, 255}, {18, 137, 151, 255}, {18, 141, 153, 255}, {16, 141, 154, 255}, {15, 142, 156, 255}, {16, 145, 159, 255}, {16, 145, 160, 255}, {15, 146, 161, 255}, {14, 148, 162, 255}, {14, 149, 163, 255}, {14, 149, 163, 255}, {14, 150, 165, 255}, {13, 150, 165, 255}, {12, 151, 166, 255}, {12, 152, 166, 255}, {12, 151, 166, 255}, {11, 151, 166, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {11, 153, 168, 255}, {11, 153, 168, 255}, {11, 154, 169, 255}, {12, 154, 170, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {10, 155, 170, 255}, {83, 160, 166, 255}, {66, 83, 92, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 90, 99, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 90, 99, 255}, {69, 89, 99, 255}, {72, 93, 102, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {76, 94, 103, 57}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 90, 17}, {72, 87, 94, 253}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {72, 86, 94, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {76, 99, 106, 255}, {148, 223, 231, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {136, 251, 251, 255}, {76, 209, 226, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {33, 194, 214, 255}, {41, 163, 177, 255}, {21, 132, 145, 255}, {20, 134, 146, 255}, {19, 137, 149, 255}, {19, 139, 153, 255}, {16, 141, 154, 255}, {15, 141, 155, 255}, {16, 143, 159, 255}, {16, 145, 160, 255}, {15, 145, 160, 255}, {15, 147, 161, 255}, {14, 148, 162, 255}, {14, 149, 164, 255}, {14, 150, 165, 255}, {13, 150, 165, 255}, {13, 152, 166, 255}, {13, 152, 166, 255}, {12, 151, 166, 255}, {13, 153, 167, 255}, {13, 152, 168, 255}, {12, 153, 168, 255}, {11, 153, 168, 255}, {11, 153, 168, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {35, 165, 176, 255}, {70, 92, 99, 255}, {70, 87, 96, 255}, {72, 90, 99, 255}, {70, 88, 98, 255}, {72, 90, 99, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 90, 97, 255}, {71, 90, 99, 255}, {70, 90, 97, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {70, 91, 99, 255}, {74, 95, 104, 255}, {74, 95, 104, 247}, {85, 128, 128, 6}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 86, 95, 213}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {104, 157, 162, 255}, {145, 226, 236, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {94, 220, 231, 255}, {35, 197, 217, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {24, 194, 214, 255}, {45, 177, 191, 255}, {21, 130, 143, 255}, {20, 133, 147, 255}, {20, 137, 150, 255}, {18, 139, 152, 255}, {18, 141, 154, 255}, {16, 143, 156, 255}, {16, 143, 157, 255}, {15, 145, 158, 255}, {16, 145, 161, 255}, {14, 147, 161, 255}, {14, 148, 162, 255}, {14, 149, 163, 255}, {14, 149, 164, 255}, {13, 149, 165, 255}, {13, 151, 166, 255}, {13, 152, 166, 255}, {11, 150, 166, 255}, {13, 153, 167, 255}, {13, 153, 168, 255}, {12, 153, 168, 255}, {11, 152, 167, 255}, {11, 153, 168, 255}, {12, 153, 169, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {11, 155, 169, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {10, 155, 171, 255}, {84, 143, 149, 255}, {67, 85, 92, 255}, {70, 88, 98, 255}, {72, 90, 99, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 99, 255}, {70, 89, 99, 255}, {69, 90, 98, 255}, {69, 90, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {72, 93, 101, 255}, {76, 97, 106, 195}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 87, 93, 153}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {72, 87, 94, 255}, {139, 213, 219, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {129, 243, 247, 255}, {64, 204, 222, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {13, 191, 214, 255}, {17, 192, 214, 255}, {46, 185, 200, 255}, {28, 141, 153, 255}, {21, 133, 145, 255}, {20, 136, 149, 255}, {19, 137, 151, 255}, {18, 139, 152, 255}, {17, 142, 156, 255}, {15, 142, 156, 255}, {17, 144, 160, 255}, {15, 145, 159, 255}, {15, 147, 161, 255}, {15, 148, 162, 255}, {14, 149, 163, 255}, {14, 149, 164, 255}, {13, 150, 165, 255}, {14, 150, 165, 255}, {13, 152, 166, 255}, {13, 152, 167, 255}, {12, 152, 167, 255}, {12, 152, 168, 255}, {11, 152, 167, 255}, {11, 153, 167, 255}, {12, 153, 169, 255}, {11, 153, 168, 255}, {11, 154, 169, 255}, {11, 154, 168, 255}, {11, 154, 169, 255}, {11, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 169, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {59, 169, 179, 255}, {66, 83, 91, 255}, {70, 88, 97, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {70, 89, 99, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 90, 98, 255}, {69, 90, 99, 255}, {70, 89, 98, 255}, {73, 95, 104, 132}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 89, 96, 80}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {90, 132, 137, 255}, {149, 227, 237, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {86, 213, 229, 255}, {26, 194, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {40, 191, 208, 255}, {35, 151, 165, 255}, {21, 132, 144, 255}, {20, 134, 147, 255}, {18, 137, 150, 255}, {19, 140, 152, 255}, {17, 142, 155, 255}, {16, 143, 156, 255}, {17, 143, 159, 255}, {16, 145, 159, 255}, {15, 146, 161, 255}, {15, 147, 161, 255}, {15, 148, 163, 255}, {14, 149, 164, 255}, {14, 150, 164, 255}, {14, 150, 165, 255}, {13, 151, 166, 255}, {13, 152, 166, 255}, {12, 151, 166, 255}, {13, 153, 167, 255}, {11, 152, 167, 255}, {12, 153, 168, 255}, {11, 153, 168, 255}, {12, 153, 168, 255}, {11, 154, 169, 255}, {12, 153, 169, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {12, 155, 170, 255}, {11, 154, 170, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 156, 171, 255}, {80, 119, 125, 255}, {69, 86, 95, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 99, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {71, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 90, 99, 255}, {69, 88, 97, 255}, {69, 90, 99, 255}, {70, 88, 98, 255}, {72, 89, 98, 60}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 77, 102, 10}, {71, 86, 93, 248}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {125, 194, 200, 255}, {140, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {115, 234, 241, 255}, {53, 201, 221, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {18, 192, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {31, 195, 214, 255}, {42, 164, 180, 255}, {22, 130, 143, 255}, {20, 134, 146, 255}, {19, 136, 149, 255}, {18, 138, 153, 255}, {17, 142, 154, 255}, {17, 142, 156, 255}, {17, 143, 159, 255}, {16, 145, 159, 255}, {15, 145, 160, 255}, {16, 147, 162, 255}, {14, 148, 162, 255}, {14, 149, 164, 255}, {13, 149, 164, 255}, {13, 150, 165, 255}, {13, 151, 165, 255}, {13, 151, 165, 255}, {13, 152, 167, 255}, {13, 153, 167, 255}, {12, 153, 168, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {11, 153, 168, 255}, {12, 154, 170, 255}, {12, 154, 169, 255}, {11, 154, 169, 255}, {12, 154, 170, 255}, {11, 155, 169, 255}, {11, 155, 170, 255}, {11, 154, 170, 255}, {10, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {11, 154, 169, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {77, 167, 172, 255}, {67, 84, 93, 255}, {70, 88, 96, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {70, 89, 99, 255}, {69, 90, 98, 255}, {69, 90, 99, 255}, {69, 89, 98, 255}, {69, 90, 99, 255}, {70, 88, 98, 255}, {71, 90, 100, 239}, {85, 85, 170, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {72, 86, 94, 190}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {80, 108, 114, 255}, {149, 225, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {138, 253, 253, 255}, {79, 210, 226, 255}, {20, 193, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {23, 194, 215, 255}, {46, 179, 193, 255}, {22, 132, 145, 255}, {21, 133, 147, 255}, {20, 136, 149, 255}, {19, 138, 152, 255}, {19, 140, 153, 255}, {17, 142, 156, 255}, {16, 143, 157, 255}, {16, 144, 160, 255}, {16, 145, 160, 255}, {15, 146, 161, 255}, {16, 147, 162, 255}, {14, 149, 163, 255}, {15, 149, 164, 255}, {14, 150, 164, 255}, {14, 150, 165, 255}, {13, 151, 166, 255}, {13, 152, 166, 255}, {13, 152, 167, 255}, {12, 152, 167, 255}, {11, 152, 167, 255}, {12, 153, 168, 255}, {11, 153, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {11, 154, 169, 255}, {11, 154, 170, 255}, {12, 154, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {11, 154, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {22, 160, 174, 255}, {73, 100, 107, 255}, {70, 86, 95, 255}, {72, 90, 99, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {70, 89, 99, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 97, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 90, 99, 255}, {69, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 90, 99, 255}, {72, 89, 100, 171}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 84, 93, 118}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {111, 172, 176, 255}, {142, 225, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {101, 225, 235, 255}, {41, 199, 218, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 193, 215, 255}, {45, 187, 201, 255}, {29, 141, 153, 255}, {22, 132, 145, 255}, {21, 135, 148, 255}, {19, 137, 151, 255}, {20, 140, 152, 255}, {17, 142, 156, 255}, {17, 143, 156, 255}, {16, 144, 159, 255}, {16, 145, 160, 255}, {14, 147, 161, 255}, {15, 147, 162, 255}, {15, 148, 162, 255}, {15, 149, 164, 255}, {14, 150, 164, 255}, {13, 150, 165, 255}, {13, 152, 166, 255}, {13, 151, 165, 255}, {13, 152, 167, 255}, {12, 152, 167, 255}, {12, 153, 168, 255}, {13, 153, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {11, 154, 169, 255}, {12, 154, 170, 255}, {11, 155, 169, 255}, {12, 154, 171, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 154, 169, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {86, 154, 160, 255}, {67, 84, 93, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {70, 89, 99, 255}, {69, 89, 98, 255}, {69, 90, 99, 255}, {69, 90, 99, 255}, {70, 88, 98, 255}, {72, 93, 103, 99}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 87, 93, 44}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {73, 93, 99, 255}, {147, 220, 228, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {132, 248, 250, 255}, {72, 206, 224, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {39, 193, 209, 255}, {36, 152, 166, 255}, {22, 133, 145, 255}, {20, 134, 146, 255}, {19, 138, 150, 255}, {18, 139, 152, 255}, {17, 142, 154, 255}, {16, 142, 156, 255}, {16, 143, 158, 255}, {16, 144, 160, 255}, {15, 146, 160, 255}, {15, 147, 161, 255}, {15, 148, 163, 255}, {14, 149, 164, 255}, {13, 150, 164, 255}, {13, 150, 165, 255}, {13, 151, 165, 255}, {14, 151, 166, 255}, {14, 152, 167, 255}, {12, 152, 166, 255}, {13, 152, 168, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {11, 153, 168, 255}, {12, 154, 170, 255}, {11, 154, 169, 255}, {11, 154, 169, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {11, 155, 169, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {43, 167, 178, 255}, {68, 86, 94, 255}, {69, 88, 96, 255}, {70, 88, 97, 255}, {72, 90, 99, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {71, 89, 99, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {70, 90, 98, 255}, {71, 90, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 89, 98, 255}, {70, 88, 98, 255}, {70, 90, 99, 254}, {71, 92, 102, 25}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 86, 93, 217}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {98, 147, 152, 255}, {145, 226, 236, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 225, 236, 255}, {163, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {90, 217, 230, 255}, {31, 196, 216, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {31, 195, 215, 255}, {42, 166, 180, 255}, {22, 131, 144, 255}, {21, 134, 147, 255}, {20, 136, 148, 255}, {19, 138, 153, 255}, {18, 142, 154, 255}, {17, 142, 156, 255}, {16, 143, 158, 255}, {16, 144, 160, 255}, {16, 146, 160, 255}, {15, 147, 161, 255}, {16, 147, 162, 255}, {15, 148, 163, 255}, {14, 149, 163, 255}, {15, 150, 165, 255}, {14, 150, 164, 255}, {13, 151, 166, 255}, {12, 152, 166, 255}, {13, 152, 167, 255}, {12, 152, 167, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {11, 154, 169, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {11, 155, 169, 255}, {11, 154, 170, 255}, {12, 155, 170, 255}, {11, 154, 170, 255}, {11, 154, 169, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {83, 133, 139, 255}, {68, 84, 93, 255}, {71, 89, 98, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {71, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 90, 98, 255}, {69, 90, 99, 255}, {69, 89, 97, 255}, {69, 90, 99, 255}, {70, 89, 99, 255}, {70, 91, 99, 200}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 86, 94, 133}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {135, 207, 213, 255}, {138, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 225, 236, 255}, {163, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {122, 240, 243, 255}, {60, 202, 222, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {21, 194, 214, 255}, {47, 180, 194, 255}, {22, 132, 144, 255}, {21, 134, 147, 255}, {20, 135, 149, 255}, {18, 138, 152, 255}, {19, 140, 153, 255}, {17, 141, 156, 255}, {16, 143, 156, 255}, {17, 144, 159, 255}, {16, 145, 160, 255}, {15, 147, 161, 255}, {16, 147, 162, 255}, {15, 149, 163, 255}, {14, 149, 163, 255}, {14, 149, 164, 255}, {14, 150, 165, 255}, {14, 151, 166, 255}, {13, 151, 166, 255}, {14, 152, 167, 255}, {13, 153, 167, 255}, {12, 153, 168, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {12, 153, 168, 255}, {12, 154, 170, 255}, {11, 154, 169, 255}, {11, 154, 170, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {11, 155, 170, 255}, {12, 154, 170, 255}, {11, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {66, 169, 178, 255}, {66, 84, 92, 255}, {69, 88, 96, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {70, 88, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 90, 99, 255}, {70, 89, 99, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {72, 92, 101, 114}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 85, 96, 45}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {71, 86, 93, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {85, 121, 127, 255}, {149, 228, 238, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {141, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {138, 254, 254, 255}, {84, 213, 227, 255}, {23, 194, 215, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 192, 215, 255}, {47, 188, 204, 255}, {29, 141, 154, 255}, {21, 132, 145, 255}, {21, 135, 148, 255}, {19, 137, 149, 255}, {19, 139, 152, 255}, {18, 142, 156, 255}, {17, 143, 156, 255}, {16, 143, 158, 255}, {16, 144, 160, 255}, {16, 146, 161, 255}, {16, 147, 162, 255}, {15, 148, 162, 255}, {15, 149, 164, 255}, {15, 150, 164, 255}, {14, 150, 165, 255}, {13, 151, 166, 255}, {13, 151, 166, 255}, {12, 152, 167, 255}, {13, 153, 167, 255}, {12, 152, 167, 255}, {13, 153, 169, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {11, 154, 169, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {12, 157, 172, 255}, {77, 111, 117, 255}, {67, 84, 94, 255}, {72, 90, 99, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 90, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {70, 89, 99, 255}, {70, 90, 99, 255}, {70, 89, 98, 255}, {70, 88, 98, 254}, {66, 85, 94, 27}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 86, 93, 213}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {119, 186, 191, 255}, {140, 225, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 225, 236, 255}, {163, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {108, 230, 237, 255}, {48, 199, 220, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {13, 191, 214, 255}, {40, 193, 210, 255}, {38, 155, 168, 255}, {22, 132, 144, 255}, {22, 134, 148, 255}, {20, 137, 151, 255}, {19, 138, 152, 255}, {17, 141, 153, 255}, {18, 142, 156, 255}, {16, 143, 158, 255}, {16, 144, 160, 255}, {16, 146, 161, 255}, {16, 147, 161, 255}, {15, 147, 162, 255}, {15, 149, 163, 255}, {15, 149, 163, 255}, {14, 150, 164, 255}, {13, 150, 165, 255}, {14, 152, 166, 255}, {13, 152, 166, 255}, {14, 152, 167, 255}, {13, 152, 168, 255}, {13, 153, 169, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {11, 154, 169, 255}, {12, 154, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {11, 154, 170, 255}, {12, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 169, 255}, {11, 155, 170, 255}, {12, 154, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {82, 162, 168, 255}, {66, 83, 91, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {70, 88, 98, 255}, {69, 89, 97, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {70, 89, 98, 255}, {70, 90, 99, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {71, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 90, 99, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {72, 91, 100, 196}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 84, 92, 130}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {76, 101, 107, 255}, {149, 224, 232, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {136, 252, 253, 255}, {77, 209, 224, 255}, {17, 193, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {30, 195, 215, 255}, {45, 168, 182, 255}, {23, 131, 143, 255}, {21, 134, 146, 255}, {20, 135, 147, 255}, {19, 137, 152, 255}, {18, 139, 153, 255}, {17, 142, 156, 255}, {17, 144, 157, 255}, {16, 143, 158, 255}, {16, 145, 160, 255}, {15, 147, 161, 255}, {15, 147, 161, 255}, {15, 149, 163, 255}, {15, 149, 163, 255}, {15, 150, 164, 255}, {14, 150, 165, 255}, {14, 151, 165, 255}, {14, 151, 166, 255}, {13, 152, 167, 255}, {12, 152, 167, 255}, {12, 153, 168, 255}, {12, 153, 168, 255}, {12, 153, 169, 255}, {12, 153, 168, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 154, 170, 255}, {11, 154, 170, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {30, 162, 176, 255}, {71, 94, 101, 255}, {68, 87, 95, 255}, {70, 89, 98, 255}, {72, 90, 99, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {70, 90, 97, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {69, 90, 98, 255}, {69, 90, 98, 255}, {69, 89, 98, 255}, {69, 90, 99, 255}, {70, 89, 99, 255}, {70, 89, 98, 255}, {70, 88, 100, 110}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 87, 94, 38}, {70, 85, 92, 254}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {105, 161, 167, 255}, {143, 225, 236, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {97, 221, 232, 255}, {36, 198, 217, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {21, 194, 215, 255}, {48, 181, 196, 255}, {23, 134, 145, 255}, {21, 133, 145, 255}, {22, 135, 149, 255}, {20, 138, 151, 255}, {19, 139, 153, 255}, {18, 141, 155, 255}, {17, 143, 156, 255}, {17, 144, 158, 255}, {16, 144, 160, 255}, {16, 146, 161, 255}, {15, 147, 161, 255}, {15, 148, 163, 255}, {15, 149, 164, 255}, {15, 150, 164, 255}, {15, 150, 165, 255}, {13, 150, 165, 255}, {14, 151, 165, 255}, {13, 152, 166, 255}, {14, 152, 167, 255}, {12, 152, 167, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {13, 154, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 169, 255}, {11, 155, 170, 255}, {11, 154, 169, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {85, 146, 152, 255}, {66, 84, 93, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {70, 88, 99, 255}, {70, 89, 98, 255}, {71, 90, 99, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {69, 90, 98, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 88, 99, 251}, {67, 89, 100, 23}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 85, 92, 193}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {71, 89, 95, 255}, {142, 214, 220, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {130, 245, 249, 255}, {67, 204, 222, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 193, 215, 255}, {46, 189, 205, 255}, {31, 144, 157, 255}, {22, 132, 144, 255}, {22, 135, 148, 255}, {19, 137, 149, 255}, {19, 139, 152, 255}, {19, 141, 154, 255}, {16, 141, 156, 255}, {17, 144, 158, 255}, {17, 144, 159, 255}, {16, 146, 161, 255}, {15, 147, 161, 255}, {16, 148, 162, 255}, {15, 149, 163, 255}, {15, 149, 164, 255}, {14, 149, 165, 255}, {15, 150, 165, 255}, {14, 151, 165, 255}, {13, 152, 166, 255}, {14, 152, 167, 255}, {13, 153, 167, 255}, {12, 153, 168, 255}, {13, 154, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 154, 169, 255}, {12, 154, 169, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {11, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 156, 170, 255}, {12, 155, 171, 255}, {11, 154, 169, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {53, 168, 179, 255}, {66, 84, 92, 255}, {69, 88, 96, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {71, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {70, 90, 99, 255}, {69, 88, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {71, 90, 101, 175}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 93, 93}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {92, 137, 142, 255}, {147, 226, 236, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {87, 216, 227, 255}, {26, 195, 215, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {38, 194, 210, 255}, {38, 155, 169, 255}, {22, 130, 143, 255}, {20, 133, 146, 255}, {21, 137, 150, 255}, {20, 137, 152, 255}, {19, 141, 153, 255}, {17, 141, 156, 255}, {17, 143, 157, 255}, {16, 143, 159, 255}, {16, 146, 161, 255}, {16, 146, 161, 255}, {16, 147, 162, 255}, {15, 148, 162, 255}, {15, 149, 163, 255}, {14, 150, 164, 255}, {14, 149, 165, 255}, {14, 152, 166, 255}, {13, 151, 165, 255}, {12, 151, 167, 255}, {13, 153, 167, 255}, {13, 153, 168, 255}, {14, 154, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 154, 170, 255}, {11, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 154, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {81, 123, 130, 255}, {66, 84, 94, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 90, 98, 255}, {70, 90, 100, 255}, {70, 88, 98, 255}, {71, 92, 99, 75}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {72, 86, 93, 238}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {128, 198, 204, 255}, {139, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {117, 236, 241, 255}, {55, 202, 221, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {29, 195, 214, 255}, {46, 171, 185, 255}, {23, 130, 143, 255}, {21, 134, 147, 255}, {21, 135, 149, 255}, {20, 138, 151, 255}, {19, 139, 153, 255}, {18, 141, 155, 255}, {17, 143, 156, 255}, {18, 144, 159, 255}, {17, 145, 160, 255}, {16, 146, 161, 255}, {17, 148, 163, 255}, {15, 148, 162, 255}, {15, 149, 163, 255}, {15, 149, 163, 255}, {15, 150, 165, 255}, {14, 151, 165, 255}, {13, 152, 166, 255}, {13, 151, 167, 255}, {14, 153, 167, 255}, {14, 153, 168, 255}, {12, 153, 168, 255}, {13, 153, 168, 255}, {12, 153, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 155, 172, 255}, {12, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {75, 168, 175, 255}, {65, 83, 91, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 88, 98, 255}, {71, 89, 98, 255}, {71, 89, 99, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 90, 97, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {70, 90, 97, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {70, 89, 99, 255}, {69, 89, 98, 255}, {69, 90, 98, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 88, 98, 255}, {71, 90, 100, 225}, {128, 128, 128, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 85, 94, 147}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {81, 112, 118, 255}, {148, 227, 236, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {138, 253, 254, 255}, {79, 211, 227, 255}, {20, 193, 215, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {20, 193, 215, 255}, {48, 182, 197, 255}, {24, 134, 146, 255}, {21, 132, 145, 255}, {21, 134, 148, 255}, {19, 137, 149, 255}, {20, 139, 153, 255}, {17, 140, 155, 255}, {18, 142, 156, 255}, {17, 143, 157, 255}, {17, 144, 160, 255}, {16, 146, 161, 255}, {17, 148, 163, 255}, {16, 147, 162, 255}, {15, 149, 163, 255}, {15, 149, 164, 255}, {15, 150, 165, 255}, {14, 150, 165, 255}, {14, 151, 166, 255}, {14, 152, 166, 255}, {14, 152, 167, 255}, {12, 152, 167, 255}, {14, 153, 169, 255}, {12, 153, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 170, 255}, {11, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {20, 159, 173, 255}, {73, 103, 109, 255}, {68, 86, 95, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {70, 89, 99, 255}, {70, 89, 98, 255}, {70, 89, 99, 255}, {71, 89, 99, 129}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {74, 85, 96, 45}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {112, 176, 181, 255}, {141, 225, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {103, 226, 235, 255}, {44, 199, 218, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 193, 215, 255}, {44, 188, 205, 255}, {33, 145, 157, 255}, {23, 132, 144, 255}, {20, 134, 147, 255}, {21, 137, 150, 255}, {19, 139, 151, 255}, {19, 140, 155, 255}, {18, 142, 156, 255}, {17, 143, 157, 255}, {17, 143, 159, 255}, {16, 146, 160, 255}, {16, 147, 161, 255}, {16, 147, 162, 255}, {15, 148, 163, 255}, {15, 149, 163, 255}, {15, 150, 164, 255}, {14, 150, 165, 255}, {13, 152, 166, 255}, {14, 151, 166, 255}, {12, 151, 167, 255}, {14, 153, 167, 255}, {12, 152, 168, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {12, 154, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 154, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {84, 157, 162, 255}, {65, 83, 92, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {69, 88, 97, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {70, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {70, 90, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 90, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 99, 255}, {71, 90, 99, 252}, {70, 88, 97, 29}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 86, 93, 187}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {70, 85, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {73, 95, 101, 255}, {146, 221, 230, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {135, 250, 252, 255}, {73, 208, 224, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {37, 194, 211, 255}, {39, 156, 170, 255}, {23, 130, 144, 255}, {21, 134, 147, 255}, {21, 136, 149, 255}, {20, 137, 151, 255}, {19, 140, 153, 255}, {18, 141, 155, 255}, {18, 143, 157, 255}, {17, 143, 158, 255}, {17, 145, 160, 255}, {16, 147, 161, 255}, {16, 147, 162, 255}, {17, 149, 163, 255}, {15, 149, 163, 255}, {15, 149, 163, 255}, {14, 149, 165, 255}, {15, 151, 165, 255}, {14, 151, 165, 255}, {13, 152, 167, 255}, {14, 152, 167, 255}, {13, 152, 167, 255}, {12, 153, 168, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {11, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {38, 165, 178, 255}, {68, 88, 95, 255}, {70, 88, 96, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {70, 89, 99, 255}, {69, 89, 99, 255}, {69, 90, 98, 255}, {69, 89, 98, 255}, {69, 90, 98, 255}, {69, 90, 99, 255}, {70, 88, 98, 255}, {71, 89, 98, 169}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 87, 94, 73}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {98, 150, 156, 255}, {144, 226, 236, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {91, 218, 231, 255}, {31, 196, 217, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {27, 194, 214, 255}, {45, 171, 185, 255}, {23, 130, 143, 255}, {22, 132, 145, 255}, {22, 135, 149, 255}, {20, 137, 151, 255}, {19, 138, 153, 255}, {18, 141, 154, 255}, {18, 142, 156, 255}, {17, 143, 158, 255}, {17, 145, 159, 255}, {16, 146, 160, 255}, {15, 147, 161, 255}, {15, 147, 161, 255}, {15, 148, 163, 255}, {15, 149, 164, 255}, {14, 149, 165, 255}, {14, 150, 165, 255}, {14, 151, 166, 255}, {14, 152, 166, 255}, {14, 152, 167, 255}, {13, 152, 167, 255}, {12, 153, 168, 255}, {13, 154, 168, 255}, {12, 153, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {84, 138, 144, 255}, {67, 83, 92, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 88, 97, 255}, {70, 89, 97, 255}, {71, 89, 98, 255}, {69, 88, 97, 255}, {70, 88, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {71, 89, 99, 255}, {70, 89, 97, 255}, {69, 88, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 89, 97, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 90, 98, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 89, 99, 255}, {71, 90, 99, 54}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 86, 92, 211}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {70, 85, 93, 255}, {136, 210, 216, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {125, 241, 245, 255}, {62, 203, 222, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {19, 194, 215, 255}, {47, 183, 197, 255}, {26, 135, 147, 255}, {23, 133, 144, 255}, {21, 134, 148, 255}, {20, 136, 150, 255}, {20, 138, 153, 255}, {18, 140, 154, 255}, {18, 142, 155, 255}, {16, 143, 157, 255}, {18, 145, 159, 255}, {16, 146, 161, 255}, {17, 147, 161, 255}, {16, 147, 162, 255}, {16, 149, 164, 255}, {15, 149, 164, 255}, {14, 149, 164, 255}, {14, 150, 165, 255}, {14, 151, 165, 255}, {14, 152, 166, 255}, {14, 152, 167, 255}, {14, 153, 167, 255}, {13, 153, 168, 255}, {12, 153, 168, 255}, {13, 153, 169, 255}, {12, 153, 168, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {62, 169, 178, 255}, {65, 82, 90, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 90, 97, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 100, 194}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 84, 92, 97}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {86, 125, 131, 255}, {148, 227, 237, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {84, 212, 228, 255}, {23, 194, 216, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 192, 215, 255}, {44, 189, 205, 255}, {34, 145, 159, 255}, {23, 131, 143, 255}, {22, 134, 147, 255}, {20, 136, 149, 255}, {20, 139, 151, 255}, {17, 140, 154, 255}, {18, 142, 155, 255}, {18, 143, 157, 255}, {18, 144, 158, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {16, 147, 162, 255}, {15, 148, 162, 255}, {15, 148, 163, 255}, {15, 151, 165, 255}, {14, 150, 165, 255}, {16, 151, 166, 255}, {14, 151, 165, 255}, {14, 152, 166, 255}, {13, 152, 167, 255}, {14, 153, 168, 255}, {12, 153, 168, 255}, {13, 153, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {78, 115, 121, 255}, {68, 85, 93, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {71, 89, 98, 255}, {69, 88, 96, 255}, {71, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 90, 99, 255}, {70, 88, 98, 255}, {71, 90, 100, 79}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {102, 102, 102, 5}, {70, 85, 92, 229}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {122, 189, 194, 255}, {139, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {111, 232, 238, 255}, {49, 201, 220, 255}, {16, 192, 214, 255}, {17, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {37, 195, 212, 255}, {40, 159, 173, 255}, {23, 130, 144, 255}, {21, 133, 145, 255}, {19, 135, 147, 255}, {20, 137, 151, 255}, {18, 139, 153, 255}, {18, 141, 155, 255}, {18, 143, 157, 255}, {17, 143, 158, 255}, {16, 144, 159, 255}, {16, 146, 161, 255}, {16, 146, 161, 255}, {16, 148, 163, 255}, {16, 150, 164, 255}, {14, 148, 163, 255}, {15, 150, 164, 255}, {14, 150, 165, 255}, {14, 151, 166, 255}, {13, 152, 166, 255}, {13, 152, 167, 255}, {14, 153, 167, 255}, {12, 153, 168, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {12, 154, 170, 255}, {12, 154, 169, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {79, 164, 170, 255}, {66, 83, 90, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {71, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 90, 99, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {69, 89, 98, 255}, {70, 89, 100, 215}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 92, 111}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {78, 104, 110, 255}, {148, 225, 232, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {137, 252, 253, 255}, {77, 210, 225, 255}, {17, 192, 215, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {26, 194, 213, 255}, {46, 173, 186, 255}, {24, 129, 143, 255}, {22, 132, 145, 255}, {21, 135, 149, 255}, {20, 136, 151, 255}, {20, 139, 152, 255}, {18, 140, 154, 255}, {18, 142, 155, 255}, {18, 143, 158, 255}, {17, 144, 158, 255}, {17, 146, 161, 255}, {16, 148, 162, 255}, {16, 147, 162, 255}, {16, 149, 163, 255}, {15, 149, 164, 255}, {14, 150, 164, 255}, {14, 150, 165, 255}, {15, 151, 167, 255}, {14, 151, 166, 255}, {14, 152, 167, 255}, {14, 153, 167, 255}, {14, 153, 168, 255}, {13, 154, 168, 255}, {13, 153, 169, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {27, 162, 175, 255}, {71, 95, 103, 255}, {68, 85, 94, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {71, 89, 98, 255}, {69, 88, 97, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 90, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 88, 97, 255}, {70, 89, 99, 255}, {69, 88, 98, 255}, {69, 89, 97, 255}, {70, 88, 98, 255}, {70, 90, 99, 255}, {70, 88, 98, 255}, {72, 91, 100, 92}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 6}, {69, 84, 92, 227}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {108, 166, 171, 255}, {142, 225, 236, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {97, 224, 234, 255}, {39, 198, 218, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {18, 193, 215, 255}, {47, 184, 199, 255}, {26, 135, 145, 255}, {23, 133, 144, 255}, {21, 134, 147, 255}, {21, 137, 150, 255}, {19, 138, 152, 255}, {17, 139, 154, 255}, {18, 142, 155, 255}, {17, 143, 157, 255}, {18, 144, 158, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {16, 148, 162, 255}, {16, 147, 162, 255}, {16, 150, 164, 255}, {15, 149, 163, 255}, {14, 149, 165, 255}, {14, 151, 165, 255}, {14, 152, 166, 255}, {13, 152, 166, 255}, {14, 152, 167, 255}, {12, 152, 168, 255}, {12, 153, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {85, 151, 156, 255}, {67, 83, 91, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {71, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {71, 89, 99, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {70, 91, 99, 214}, {128, 128, 128, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 87, 92, 105}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {71, 89, 96, 255}, {143, 217, 224, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {131, 247, 249, 255}, {69, 207, 223, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 192, 215, 255}, {43, 190, 207, 255}, {34, 147, 160, 255}, {25, 132, 143, 255}, {22, 134, 147, 255}, {20, 136, 149, 255}, {20, 137, 151, 255}, {18, 139, 154, 255}, {19, 141, 154, 255}, {18, 143, 157, 255}, {18, 144, 158, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {17, 148, 162, 255}, {16, 147, 162, 255}, {15, 149, 163, 255}, {15, 149, 163, 255}, {14, 150, 165, 255}, {15, 150, 165, 255}, {15, 152, 167, 255}, {14, 152, 166, 255}, {13, 152, 167, 255}, {14, 153, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {12, 154, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {48, 167, 178, 255}, {66, 84, 92, 255}, {68, 88, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 89, 97, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {70, 89, 99, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 90, 99, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {69, 88, 98, 255}, {70, 88, 100, 87}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {102, 102, 102, 5}, {70, 85, 93, 223}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {94, 140, 147, 255}, {146, 226, 236, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {88, 216, 230, 255}, {27, 194, 216, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {35, 195, 212, 255}, {40, 160, 173, 255}, {24, 129, 143, 255}, {22, 133, 145, 255}, {20, 134, 148, 255}, {20, 136, 151, 255}, {19, 139, 152, 255}, {19, 140, 154, 255}, {18, 143, 156, 255}, {17, 143, 157, 255}, {17, 144, 158, 255}, {16, 145, 161, 255}, {15, 146, 161, 255}, {17, 148, 163, 255}, {16, 149, 163, 255}, {16, 150, 165, 255}, {16, 151, 165, 255}, {15, 150, 165, 255}, {14, 151, 165, 255}, {14, 151, 166, 255}, {13, 151, 167, 255}, {14, 153, 167, 255}, {13, 152, 167, 255}, {14, 153, 168, 255}, {13, 153, 169, 255}, {12, 153, 169, 255}, {13, 154, 169, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {13, 156, 170, 255}, {12, 155, 171, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {83, 128, 134, 255}, {66, 85, 92, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {71, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 90, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {70, 90, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 99, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 90, 99, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {71, 89, 99, 209}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 85, 93, 99}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 84, 92, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {131, 201, 208, 255}, {137, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {120, 239, 243, 255}, {57, 202, 222, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {24, 194, 214, 255}, {46, 175, 188, 255}, {23, 129, 142, 255}, {22, 132, 145, 255}, {21, 134, 148, 255}, {20, 136, 150, 255}, {20, 138, 151, 255}, {18, 140, 154, 255}, {18, 142, 155, 255}, {16, 143, 157, 255}, {17, 144, 158, 255}, {17, 146, 161, 255}, {16, 147, 161, 255}, {16, 148, 162, 255}, {16, 148, 163, 255}, {15, 149, 164, 255}, {15, 149, 163, 255}, {14, 150, 165, 255}, {14, 151, 165, 255}, {14, 152, 166, 255}, {13, 152, 166, 255}, {13, 153, 167, 255}, {14, 153, 167, 255}, {13, 154, 168, 255}, {13, 153, 168, 255}, {12, 153, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 154, 170, 255}, {13, 155, 170, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {70, 169, 176, 255}, {65, 83, 91, 255}, {69, 87, 96, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {71, 89, 98, 255}, {70, 88, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {69, 90, 98, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {72, 91, 101, 81}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {128, 128, 128, 2}, {70, 85, 92, 205}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {82, 115, 121, 255}, {148, 227, 237, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {137, 254, 254, 255}, {81, 212, 227, 255}, {20, 194, 215, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {18, 192, 214, 255}, {47, 185, 198, 255}, {27, 137, 149, 255}, {24, 132, 144, 255}, {22, 134, 147, 255}, {21, 137, 150, 255}, {20, 137, 151, 255}, {19, 139, 154, 255}, {18, 141, 154, 255}, {18, 143, 157, 255}, {18, 144, 158, 255}, {17, 146, 160, 255}, {17, 147, 161, 255}, {16, 146, 161, 255}, {16, 148, 162, 255}, {15, 149, 163, 255}, {16, 150, 165, 255}, {14, 150, 164, 255}, {15, 150, 165, 255}, {15, 152, 167, 255}, {14, 152, 166, 255}, {14, 152, 168, 255}, {14, 153, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {13, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {14, 157, 172, 255}, {75, 107, 112, 255}, {68, 85, 94, 255}, {70, 88, 97, 255}, {68, 88, 96, 255}, {70, 89, 98, 255}, {70, 88, 98, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {70, 89, 99, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {70, 88, 98, 255}, {70, 88, 98, 255}, {70, 90, 100, 189}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 83, 93, 55}, {70, 84, 92, 253}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {115, 180, 185, 255}, {140, 225, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {105, 228, 237, 255}, {45, 200, 219, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 192, 215, 255}, {43, 190, 207, 255}, {35, 149, 160, 255}, {25, 130, 143, 255}, {22, 133, 145, 255}, {20, 136, 149, 255}, {21, 138, 151, 255}, {19, 139, 152, 255}, {18, 140, 154, 255}, {19, 143, 157, 255}, {16, 143, 157, 255}, {18, 145, 160, 255}, {17, 147, 160, 255}, {16, 147, 161, 255}, {16, 148, 162, 255}, {15, 148, 162, 255}, {16, 150, 165, 255}, {15, 149, 164, 255}, {14, 150, 165, 255}, {15, 151, 166, 255}, {14, 151, 165, 255}, {13, 152, 167, 255}, {14, 152, 167, 255}, {13, 152, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {13, 155, 170, 255}, {13, 154, 169, 255}, {12, 155, 170, 255}, {13, 155, 170, 255}, {12, 155, 170, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {13, 156, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {83, 160, 166, 255}, {66, 83, 91, 255}, {68, 89, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {71, 89, 98, 255}, {70, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 87, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 87, 97, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {70, 89, 98, 249}, {70, 89, 102, 40}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 84, 91, 154}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {73, 97, 103, 255}, {146, 223, 230, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {135, 251, 251, 255}, {75, 209, 225, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {33, 194, 214, 255}, {44, 163, 175, 255}, {24, 129, 143, 255}, {22, 132, 144, 255}, {21, 134, 147, 255}, {20, 136, 151, 255}, {19, 139, 152, 255}, {19, 139, 154, 255}, {18, 142, 156, 255}, {17, 143, 157, 255}, {17, 143, 158, 255}, {17, 146, 160, 255}, {17, 147, 161, 255}, {17, 148, 163, 255}, {15, 147, 161, 255}, {15, 149, 163, 255}, {15, 149, 164, 255}, {15, 151, 166, 255}, {15, 151, 165, 255}, {14, 151, 165, 255}, {14, 152, 166, 255}, {14, 152, 167, 255}, {14, 152, 168, 255}, {13, 153, 168, 255}, {12, 153, 168, 255}, {13, 154, 169, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {34, 164, 176, 255}, {69, 90, 98, 255}, {68, 87, 96, 255}, {70, 88, 97, 255}, {69, 88, 96, 255}, {68, 89, 97, 255}, {70, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 87, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {70, 88, 98, 255}, {72, 91, 100, 135}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {75, 90, 90, 17}, {70, 84, 93, 234}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {101, 156, 161, 255}, {143, 226, 236, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {140, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {93, 220, 231, 255}, {32, 196, 217, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {25, 194, 214, 255}, {47, 177, 191, 255}, {24, 128, 141, 255}, {22, 131, 144, 255}, {22, 134, 148, 255}, {21, 137, 150, 255}, {21, 138, 151, 255}, {18, 139, 154, 255}, {19, 141, 154, 255}, {18, 143, 157, 255}, {17, 144, 157, 255}, {17, 145, 160, 255}, {17, 147, 161, 255}, {17, 147, 162, 255}, {16, 148, 162, 255}, {15, 149, 163, 255}, {16, 150, 164, 255}, {16, 151, 166, 255}, {15, 150, 165, 255}, {15, 151, 166, 255}, {14, 152, 166, 255}, {15, 152, 168, 255}, {14, 153, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {84, 142, 148, 255}, {65, 84, 91, 255}, {70, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 88, 96, 255}, {71, 89, 98, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {69, 89, 99, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {71, 90, 99, 224}, {77, 102, 102, 10}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 85, 93, 99}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {70, 85, 92, 255}, {137, 212, 219, 255}, {136, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {128, 243, 247, 255}, {63, 204, 222, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {18, 192, 214, 255}, {46, 186, 200, 255}, {30, 139, 151, 255}, {23, 131, 143, 255}, {22, 133, 146, 255}, {21, 135, 149, 255}, {20, 137, 151, 255}, {18, 139, 153, 255}, {18, 141, 154, 255}, {19, 143, 157, 255}, {16, 143, 157, 255}, {18, 145, 160, 255}, {18, 147, 161, 255}, {17, 147, 161, 255}, {15, 147, 162, 255}, {16, 149, 163, 255}, {16, 150, 164, 255}, {15, 150, 164, 255}, {15, 150, 165, 255}, {14, 151, 165, 255}, {15, 151, 166, 255}, {14, 152, 166, 255}, {14, 153, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 153, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 156, 170, 255}, {13, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {59, 169, 179, 255}, {65, 82, 90, 255}, {69, 87, 96, 255}, {69, 88, 96, 255}, {70, 89, 98, 255}, {68, 88, 96, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 87, 97, 255}, {70, 88, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {70, 88, 98, 255}, {72, 91, 101, 81}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 84, 91, 197}, {69, 83, 91, 255}, {69, 83, 91, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {88, 130, 135, 255}, {147, 227, 237, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {84, 213, 229, 255}, {24, 194, 215, 255}, {16, 192, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {42, 191, 207, 255}, {36, 149, 162, 255}, {24, 130, 142, 255}, {23, 132, 144, 255}, {21, 135, 148, 255}, {21, 137, 151, 255}, {19, 139, 152, 255}, {18, 140, 154, 255}, {19, 142, 155, 255}, {18, 143, 158, 255}, {17, 145, 159, 255}, {17, 146, 161, 255}, {16, 146, 161, 255}, {16, 147, 162, 255}, {16, 148, 163, 255}, {15, 149, 163, 255}, {16, 150, 165, 255}, {15, 150, 165, 255}, {15, 151, 165, 255}, {15, 151, 166, 255}, {14, 152, 166, 255}, {14, 152, 167, 255}, {13, 152, 168, 255}, {14, 154, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 156, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 156, 171, 255}, {78, 118, 123, 255}, {67, 84, 93, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {71, 89, 98, 255}, {70, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {70, 88, 98, 255}, {69, 88, 98, 255}, {68, 88, 96, 255}, {69, 89, 97, 255}, {71, 89, 98, 180}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 83, 89, 46}, {70, 84, 91, 251}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {124, 194, 198, 255}, {138, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {114, 234, 240, 255}, {51, 201, 221, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {32, 195, 214, 255}, {44, 165, 179, 255}, {24, 129, 141, 255}, {22, 132, 144, 255}, {23, 134, 146, 255}, {21, 135, 151, 255}, {19, 138, 151, 255}, {18, 139, 153, 255}, {18, 142, 155, 255}, {18, 143, 157, 255}, {17, 143, 157, 255}, {17, 145, 160, 255}, {16, 146, 160, 255}, {17, 147, 162, 255}, {15, 147, 161, 255}, {16, 150, 164, 255}, {15, 149, 164, 255}, {15, 151, 165, 255}, {15, 150, 165, 255}, {15, 151, 166, 255}, {15, 151, 166, 255}, {14, 152, 167, 255}, {14, 153, 167, 255}, {14, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {13, 154, 169, 255}, {12, 153, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {12, 154, 171, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {77, 167, 172, 255}, {65, 82, 91, 255}, {70, 88, 97, 255}, {69, 88, 96, 255}, {70, 88, 97, 255}, {68, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 246}, {70, 85, 100, 33}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 83, 92, 144}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {77, 105, 112, 255}, {148, 225, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {137, 253, 253, 255}, {78, 209, 226, 255}, {18, 193, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {24, 194, 215, 255}, {47, 178, 192, 255}, {23, 129, 142, 255}, {23, 130, 143, 255}, {22, 133, 147, 255}, {21, 136, 149, 255}, {20, 137, 151, 255}, {19, 139, 153, 255}, {18, 140, 154, 255}, {18, 143, 156, 255}, {18, 143, 158, 255}, {18, 145, 160, 255}, {17, 147, 160, 255}, {17, 147, 161, 255}, {17, 148, 163, 255}, {16, 148, 162, 255}, {16, 150, 165, 255}, {15, 150, 165, 255}, {14, 150, 165, 255}, {15, 151, 166, 255}, {14, 151, 165, 255}, {14, 152, 167, 255}, {13, 152, 166, 255}, {13, 152, 167, 255}, {13, 153, 168, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 170, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {22, 160, 174, 255}, {72, 98, 105, 255}, {69, 85, 94, 255}, {68, 89, 97, 255}, {69, 88, 96, 255}, {68, 88, 96, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 90, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 90, 98, 125}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 85, 85, 12}, {70, 85, 92, 229}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {109, 170, 176, 255}, {141, 225, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 225, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {99, 224, 235, 255}, {39, 198, 218, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {17, 193, 215, 255}, {47, 186, 202, 255}, {29, 140, 151, 255}, {24, 130, 143, 255}, {22, 134, 146, 255}, {20, 134, 149, 255}, {21, 138, 151, 255}, {19, 139, 153, 255}, {18, 139, 154, 255}, {18, 142, 155, 255}, {18, 143, 157, 255}, {18, 145, 159, 255}, {17, 146, 160, 255}, {16, 146, 160, 255}, {17, 148, 163, 255}, {16, 147, 162, 255}, {16, 150, 164, 255}, {16, 150, 165, 255}, {14, 149, 165, 255}, {15, 151, 165, 255}, {14, 151, 166, 255}, {13, 152, 166, 255}, {15, 152, 168, 255}, {14, 152, 168, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 153, 169, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {85, 154, 159, 255}, {65, 82, 91, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {70, 89, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {70, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {70, 89, 98, 255}, {71, 89, 99, 217}, {73, 109, 109, 7}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {68, 83, 92, 86}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {69, 83, 91, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {71, 90, 97, 255}, {145, 220, 227, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {131, 248, 250, 255}, {71, 206, 224, 255}, {15, 191, 214, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {42, 194, 209, 255}, {37, 152, 165, 255}, {23, 130, 142, 255}, {22, 132, 145, 255}, {21, 134, 148, 255}, {22, 137, 150, 255}, {19, 138, 151, 255}, {18, 139, 153, 255}, {19, 142, 155, 255}, {18, 143, 157, 255}, {17, 144, 159, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {17, 147, 162, 255}, {16, 148, 162, 255}, {16, 149, 164, 255}, {15, 150, 164, 255}, {16, 151, 165, 255}, {14, 150, 165, 255}, {15, 151, 167, 255}, {14, 152, 166, 255}, {15, 152, 168, 255}, {13, 152, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 156, 170, 255}, {13, 156, 170, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {42, 167, 177, 255}, {67, 85, 93, 255}, {69, 88, 96, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {70, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {70, 88, 98, 255}, {68, 89, 96, 255}, {69, 89, 98, 254}, {71, 90, 98, 68}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 86, 90, 155}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {95, 146, 151, 255}, {144, 225, 236, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 225, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {89, 216, 230, 255}, {29, 195, 216, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {31, 195, 215, 255}, {45, 166, 180, 255}, {23, 129, 141, 255}, {22, 132, 145, 255}, {23, 134, 146, 255}, {21, 135, 151, 255}, {19, 138, 151, 255}, {19, 139, 153, 255}, {19, 141, 155, 255}, {18, 143, 156, 255}, {18, 144, 157, 255}, {16, 144, 159, 255}, {17, 146, 161, 255}, {16, 147, 161, 255}, {16, 147, 162, 255}, {16, 149, 163, 255}, {15, 149, 163, 255}, {16, 150, 165, 255}, {14, 149, 165, 255}, {14, 151, 166, 255}, {15, 151, 167, 255}, {14, 152, 167, 255}, {13, 152, 167, 255}, {13, 152, 167, 255}, {13, 153, 168, 255}, {13, 153, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {13, 155, 170, 255}, {12, 154, 171, 255}, {13, 156, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {83, 133, 139, 255}, {66, 84, 91, 255}, {69, 88, 96, 255}, {69, 89, 98, 255}, {69, 88, 96, 255}, {68, 89, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 87, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 97, 255}, {70, 90, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 90, 99, 136}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 96, 96, 8}, {68, 83, 91, 210}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {134, 206, 213, 255}, {136, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {121, 239, 243, 255}, {58, 202, 222, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {23, 194, 214, 255}, {47, 180, 193, 255}, {24, 131, 143, 255}, {22, 130, 143, 255}, {22, 134, 147, 255}, {21, 135, 148, 255}, {20, 137, 151, 255}, {19, 138, 152, 255}, {18, 140, 153, 255}, {18, 142, 155, 255}, {17, 142, 157, 255}, {18, 145, 159, 255}, {16, 145, 160, 255}, {16, 146, 161, 255}, {17, 148, 163, 255}, {16, 147, 162, 255}, {16, 150, 164, 255}, {16, 150, 165, 255}, {15, 150, 165, 255}, {14, 150, 165, 255}, {15, 151, 167, 255}, {14, 151, 166, 255}, {14, 152, 167, 255}, {14, 153, 167, 255}, {13, 154, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {12, 154, 170, 255}, {13, 154, 169, 255}, {13, 155, 171, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {65, 169, 177, 255}, {65, 83, 91, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {69, 89, 99, 255}, {68, 87, 97, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 98, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 97, 255}, {70, 91, 100, 197}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {70, 83, 89, 40}, {68, 83, 91, 243}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {83, 120, 125, 255}, {148, 227, 238, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 224, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {137, 254, 254, 255}, {81, 212, 227, 255}, {21, 193, 215, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 192, 215, 255}, {47, 187, 204, 255}, {31, 140, 151, 255}, {23, 130, 142, 255}, {23, 133, 146, 255}, {20, 134, 148, 255}, {21, 137, 150, 255}, {20, 140, 153, 255}, {19, 140, 153, 255}, {18, 141, 155, 255}, {18, 143, 156, 255}, {17, 145, 158, 255}, {17, 145, 160, 255}, {17, 147, 160, 255}, {17, 147, 162, 255}, {17, 148, 163, 255}, {16, 150, 164, 255}, {16, 150, 165, 255}, {15, 150, 164, 255}, {14, 150, 165, 255}, {15, 151, 167, 255}, {13, 152, 166, 255}, {14, 152, 167, 255}, {14, 152, 168, 255}, {14, 152, 168, 255}, {13, 153, 168, 255}, {13, 153, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {13, 155, 171, 255}, {13, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 170, 255}, {13, 156, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {12, 157, 172, 255}, {77, 110, 117, 255}, {66, 84, 93, 255}, {69, 88, 96, 255}, {70, 88, 97, 255}, {68, 89, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 99, 255}, {69, 87, 97, 255}, {68, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {70, 89, 98, 236}, {73, 91, 100, 28}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 83, 91, 92}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {119, 185, 190, 255}, {138, 225, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {108, 230, 238, 255}, {47, 200, 219, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {41, 193, 210, 255}, {38, 155, 168, 255}, {23, 130, 142, 255}, {23, 131, 144, 255}, {21, 134, 147, 255}, {21, 135, 150, 255}, {19, 138, 151, 255}, {19, 140, 153, 255}, {18, 141, 155, 255}, {18, 143, 156, 255}, {17, 145, 158, 255}, {17, 145, 159, 255}, {17, 147, 160, 255}, {16, 147, 161, 255}, {17, 148, 163, 255}, {16, 149, 163, 255}, {16, 150, 164, 255}, {15, 149, 163, 255}, {14, 149, 165, 255}, {15, 151, 165, 255}, {14, 151, 165, 255}, {14, 152, 167, 255}, {14, 153, 168, 255}, {14, 152, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {81, 162, 169, 255}, {64, 82, 90, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {68, 89, 97, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {68, 89, 97, 255}, {70, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 89, 99, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {68, 89, 97, 254}, {70, 91, 97, 76}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 84, 90, 158}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {75, 99, 105, 255}, {146, 223, 232, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 224, 236, 255}, {162, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {135, 252, 253, 255}, {76, 208, 223, 255}, {16, 192, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {31, 195, 215, 255}, {45, 168, 182, 255}, {24, 129, 141, 255}, {23, 131, 145, 255}, {23, 133, 146, 255}, {21, 135, 149, 255}, {19, 138, 151, 255}, {19, 138, 152, 255}, {18, 141, 154, 255}, {19, 142, 156, 255}, {18, 143, 157, 255}, {18, 145, 159, 255}, {17, 146, 161, 255}, {16, 147, 161, 255}, {17, 148, 163, 255}, {16, 148, 163, 255}, {16, 150, 164, 255}, {15, 149, 164, 255}, {15, 150, 164, 255}, {16, 151, 166, 255}, {15, 151, 167, 255}, {14, 152, 166, 255}, {15, 152, 168, 255}, {14, 153, 167, 255}, {13, 154, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {13, 157, 171, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {29, 162, 175, 255}, {70, 93, 100, 255}, {68, 86, 94, 255}, {69, 88, 96, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 89, 97, 255}, {69, 87, 97, 255}, {70, 89, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {68, 88, 96, 255}, {70, 88, 98, 255}, {70, 90, 99, 139}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {69, 83, 91, 212}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {104, 160, 165, 255}, {142, 224, 236, 255}, {135, 224, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {95, 222, 233, 255}, {33, 197, 217, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {22, 194, 215, 255}, {48, 181, 195, 255}, {24, 133, 145, 255}, {22, 130, 143, 255}, {22, 133, 146, 255}, {20, 135, 148, 255}, {20, 137, 151, 255}, {19, 137, 152, 255}, {19, 140, 153, 255}, {18, 141, 155, 255}, {18, 143, 156, 255}, {18, 145, 159, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {16, 148, 162, 255}, {16, 147, 162, 255}, {16, 150, 164, 255}, {15, 149, 163, 255}, {15, 150, 165, 255}, {14, 150, 165, 255}, {15, 151, 167, 255}, {13, 152, 166, 255}, {13, 151, 167, 255}, {15, 153, 168, 255}, {14, 153, 168, 255}, {13, 153, 168, 255}, {14, 153, 168, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 171, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {12, 154, 170, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {84, 146, 152, 255}, {65, 84, 92, 255}, {69, 88, 96, 255}, {70, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {70, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {68, 88, 97, 255}, {69, 88, 98, 255}, {69, 90, 99, 199}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {68, 81, 93, 41}, {69, 84, 91, 243}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {68, 86, 93, 255}, {140, 215, 221, 255}, {135, 223, 235, 255}, {135, 224, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {130, 246, 250, 255}, {65, 204, 222, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 193, 215, 255}, {46, 189, 204, 255}, {32, 145, 155, 255}, {24, 130, 142, 255}, {23, 132, 144, 255}, {21, 134, 148, 255}, {21, 136, 149, 255}, {19, 138, 151, 255}, {19, 140, 153, 255}, {19, 142, 155, 255}, {18, 142, 156, 255}, {18, 145, 158, 255}, {18, 146, 159, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {17, 148, 163, 255}, {16, 149, 163, 255}, {16, 150, 164, 255}, {15, 149, 164, 255}, {16, 151, 166, 255}, {15, 151, 166, 255}, {14, 151, 166, 255}, {14, 152, 167, 255}, {15, 153, 168, 255}, {14, 152, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {12, 155, 170, 255}, {13, 156, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {52, 168, 177, 255}, {65, 83, 91, 255}, {69, 87, 95, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {68, 88, 96, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 89, 99, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {70, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {69, 88, 98, 255}, {71, 89, 99, 237}, {70, 88, 97, 29}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 83, 91, 95}, {67, 82, 90, 255}, {68, 83, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {89, 135, 140, 255}, {145, 225, 236, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {138, 255, 255, 255}, {87, 217, 229, 255}, {25, 194, 215, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {39, 193, 210, 255}, {38, 155, 169, 255}, {24, 129, 142, 255}, {23, 131, 143, 255}, {22, 134, 147, 255}, {20, 134, 149, 255}, {20, 138, 151, 255}, {20, 140, 153, 255}, {19, 141, 155, 255}, {18, 142, 156, 255}, {17, 143, 158, 255}, {18, 145, 159, 255}, {18, 146, 161, 255}, {16, 146, 161, 255}, {17, 147, 162, 255}, {15, 148, 162, 255}, {16, 150, 164, 255}, {15, 149, 164, 255}, {16, 151, 165, 255}, {15, 151, 166, 255}, {16, 152, 167, 255}, {13, 152, 166, 255}, {14, 152, 167, 255}, {14, 152, 168, 255}, {14, 153, 169, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 154, 169, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {13, 155, 171, 255}, {13, 156, 170, 255}, {13, 156, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {80, 122, 129, 255}, {67, 84, 93, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {68, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 89, 98, 255}, {68, 87, 97, 255}, {68, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {68, 88, 97, 255}, {69, 88, 97, 254}, {70, 89, 96, 77}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 83, 91, 157}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {127, 197, 205, 255}, {137, 223, 235, 255}, {135, 223, 235, 255}, {135, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {138, 255, 255, 255}, {139, 255, 255, 255}, {138, 255, 255, 255}, {118, 239, 244, 255}, {54, 201, 220, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {29, 196, 215, 255}, {46, 170, 185, 255}, {24, 129, 141, 255}, {24, 131, 144, 255}, {23, 133, 146, 255}, {21, 134, 148, 255}, {21, 137, 151, 255}, {19, 138, 151, 255}, {19, 139, 154, 255}, {19, 142, 155, 255}, {17, 143, 157, 255}, {17, 145, 158, 255}, {18, 146, 160, 255}, {17, 147, 160, 255}, {17, 147, 162, 255}, {15, 147, 161, 255}, {16, 149, 163, 255}, {16, 150, 164, 255}, {15, 150, 164, 255}, {15, 151, 166, 255}, {15, 151, 165, 255}, {14, 152, 166, 255}, {14, 152, 167, 255}, {13, 152, 167, 255}, {14, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 153, 168, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 156, 170, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {13, 155, 171, 255}, {12, 155, 171, 255}, {12, 154, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {72, 167, 175, 255}, {66, 83, 90, 255}, {69, 87, 95, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 87, 96, 255}, {70, 89, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 89, 98, 255}, {69, 89, 98, 255}, {68, 87, 97, 255}, {69, 89, 99, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {68, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {68, 88, 96, 255}, {69, 88, 97, 255}, {71, 89, 101, 137}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 128, 128, 4}, {69, 83, 91, 182}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {78, 110, 115, 255}, {146, 226, 236, 255}, {135, 223, 235, 255}, {134, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {136, 254, 254, 255}, {79, 211, 227, 255}, {17, 192, 215, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {20, 194, 216, 255}, {48, 182, 197, 255}, {25, 133, 147, 255}, {22, 130, 143, 255}, {24, 131, 145, 255}, {21, 134, 147, 255}, {21, 136, 150, 255}, {19, 137, 152, 255}, {19, 140, 153, 255}, {19, 141, 155, 255}, {19, 143, 157, 255}, {17, 144, 158, 255}, {18, 145, 160, 255}, {17, 146, 161, 255}, {17, 147, 162, 255}, {16, 148, 162, 255}, {15, 148, 162, 255}, {16, 150, 165, 255}, {16, 150, 165, 255}, {14, 149, 165, 255}, {16, 151, 166, 255}, {14, 151, 165, 255}, {14, 152, 166, 255}, {14, 153, 167, 255}, {14, 153, 168, 255}, {14, 153, 168, 255}, {13, 154, 169, 255}, {13, 153, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {13, 156, 171, 255}, {12, 154, 170, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {16, 159, 172, 255}, {73, 103, 109, 255}, {67, 85, 93, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {68, 89, 97, 255}, {69, 89, 97, 255}, {70, 89, 97, 255}, {69, 87, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {70, 90, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 88, 98, 255}, {69, 89, 98, 255}, {69, 87, 97, 255}, {69, 89, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {70, 89, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {71, 89, 98, 166}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {68, 83, 91, 199}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {111, 175, 180, 255}, {139, 224, 235, 255}, {135, 223, 235, 255}, {134, 224, 236, 255}, {161, 255, 255, 255}, {139, 255, 255, 255}, {139, 255, 255, 255}, {102, 229, 238, 255}, {42, 198, 219, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {16, 193, 215, 255}, {45, 191, 205, 255}, {32, 146, 158, 255}, {22, 130, 143, 255}, {24, 132, 144, 255}, {22, 134, 148, 255}, {20, 135, 149, 255}, {20, 138, 152, 255}, {20, 139, 152, 255}, {18, 140, 155, 255}, {18, 142, 155, 255}, {18, 144, 158, 255}, {18, 145, 158, 255}, {16, 145, 160, 255}, {17, 147, 161, 255}, {15, 147, 161, 255}, {16, 148, 162, 255}, {15, 148, 163, 255}, {16, 150, 165, 255}, {14, 149, 165, 255}, {15, 151, 166, 255}, {14, 152, 166, 255}, {13, 152, 166, 255}, {15, 152, 168, 255}, {14, 153, 167, 255}, {14, 153, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 154, 170, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {84, 157, 163, 255}, {65, 83, 91, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {68, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 96, 255}, {69, 89, 98, 255}, {68, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 89, 99, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 88, 97, 255}, {70, 88, 98, 185}, {102, 102, 102, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {71, 85, 99, 18}, {68, 83, 91, 214}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {70, 92, 99, 255}, {145, 221, 229, 255}, {135, 223, 235, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {138, 255, 255, 255}, {134, 252, 253, 255}, {72, 207, 224, 255}, {15, 191, 214, 255}, {14, 191, 214, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {39, 196, 214, 255}, {39, 161, 175, 255}, {23, 131, 143, 255}, {23, 131, 144, 255}, {22, 134, 146, 255}, {21, 134, 148, 255}, {19, 138, 151, 255}, {19, 138, 152, 255}, {19, 140, 155, 255}, {18, 142, 155, 255}, {18, 143, 156, 255}, {16, 144, 158, 255}, {18, 145, 160, 255}, {16, 146, 161, 255}, {17, 148, 162, 255}, {16, 148, 162, 255}, {15, 148, 162, 255}, {15, 149, 164, 255}, {15, 150, 164, 255}, {15, 151, 166, 255}, {15, 151, 165, 255}, {14, 151, 166, 255}, {15, 152, 168, 255}, {14, 153, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 153, 169, 255}, {13, 153, 168, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 156, 170, 255}, {13, 155, 170, 255}, {13, 156, 170, 255}, {12, 154, 171, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 170, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {37, 166, 176, 255}, {67, 88, 94, 255}, {68, 86, 94, 255}, {69, 88, 97, 255}, {68, 88, 96, 255}, {69, 88, 96, 255}, {68, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 89, 97, 255}, {69, 87, 96, 255}, {69, 87, 96, 255}, {70, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {68, 87, 97, 255}, {69, 89, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 89, 99, 203}, {70, 93, 116, 11}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 82, 91, 28}, {69, 83, 90, 226}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {97, 150, 155, 255}, {142, 225, 236, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {138, 255, 255, 255}, {91, 220, 234, 255}, {31, 196, 217, 255}, {14, 191, 214, 255}, {15, 191, 214, 255}, {31, 197, 218, 255}, {45, 177, 191, 255}, {22, 133, 145, 255}, {22, 133, 145, 255}, {22, 134, 146, 255}, {22, 135, 148, 255}, {20, 137, 150, 255}, {19, 138, 152, 255}, {18, 139, 152, 255}, {19, 141, 155, 255}, {18, 143, 156, 255}, {17, 145, 158, 255}, {18, 145, 159, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {16, 148, 162, 255}, {16, 149, 164, 255}, {15, 149, 164, 255}, {16, 150, 165, 255}, {15, 150, 166, 255}, {14, 151, 165, 255}, {16, 152, 167, 255}, {14, 152, 166, 255}, {14, 153, 167, 255}, {13, 152, 167, 255}, {15, 153, 168, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 169, 255}, {13, 154, 170, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 170, 255}, {13, 155, 171, 255}, {13, 156, 170, 255}, {13, 156, 171, 255}, {12, 154, 170, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {84, 138, 144, 255}, {66, 83, 91, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {68, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 89, 99, 255}, {68, 87, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 90, 99, 216}, {77, 89, 102, 20}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 85, 91, 42}, {68, 83, 90, 237}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 82, 90, 255}, {134, 209, 216, 255}, {135, 224, 236, 255}, {161, 255, 255, 255}, {126, 245, 249, 255}, {60, 203, 221, 255}, {15, 191, 214, 255}, {20, 195, 216, 255}, {47, 189, 203, 255}, {23, 140, 152, 255}, {21, 134, 147, 255}, {21, 134, 147, 255}, {21, 134, 148, 255}, {20, 136, 150, 255}, {20, 138, 151, 255}, {19, 139, 152, 255}, {18, 140, 155, 255}, {18, 142, 155, 255}, {17, 143, 158, 255}, {18, 145, 159, 255}, {17, 146, 160, 255}, {16, 146, 161, 255}, {17, 148, 163, 255}, {16, 148, 162, 255}, {15, 148, 163, 255}, {14, 148, 163, 255}, {14, 150, 164, 255}, {14, 150, 165, 255}, {14, 151, 166, 255}, {15, 151, 167, 255}, {14, 152, 167, 255}, {13, 152, 167, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 153, 169, 255}, {13, 155, 171, 255}, {13, 155, 170, 255}, {12, 155, 171, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 156, 170, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {62, 169, 177, 255}, {64, 82, 90, 255}, {70, 87, 96, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 87, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 89, 98, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {68, 87, 97, 255}, {68, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 98, 229}, {66, 90, 99, 31}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {66, 84, 92, 58}, {67, 83, 90, 244}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {84, 124, 129, 255}, {147, 227, 238, 255}, {161, 255, 255, 255}, {82, 214, 228, 255}, {21, 193, 216, 255}, {46, 198, 213, 255}, {31, 153, 167, 255}, {21, 134, 148, 255}, {20, 134, 148, 255}, {21, 135, 150, 255}, {20, 136, 150, 255}, {20, 137, 151, 255}, {19, 139, 152, 255}, {18, 140, 155, 255}, {19, 142, 155, 255}, {17, 143, 158, 255}, {18, 145, 158, 255}, {18, 145, 160, 255}, {17, 147, 160, 255}, {16, 146, 161, 255}, {17, 148, 163, 255}, {15, 148, 162, 255}, {16, 150, 165, 255}, {15, 150, 164, 255}, {15, 151, 166, 255}, {14, 152, 166, 255}, {15, 151, 167, 255}, {14, 152, 167, 255}, {13, 152, 166, 255}, {13, 153, 168, 255}, {13, 154, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 156, 171, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 154, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {77, 115, 120, 255}, {67, 85, 92, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {68, 88, 96, 255}, {69, 88, 96, 255}, {68, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {68, 87, 97, 255}, {68, 87, 96, 255}, {69, 89, 98, 239}, {74, 91, 102, 45}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {68, 85, 92, 75}, {67, 83, 91, 250}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {67, 82, 90, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {111, 172, 177, 255}, {118, 237, 244, 255}, {52, 202, 220, 255}, {40, 169, 182, 255}, {20, 135, 149, 255}, {20, 135, 149, 255}, {21, 135, 150, 255}, {20, 136, 151, 255}, {19, 138, 152, 255}, {19, 139, 152, 255}, {18, 140, 154, 255}, {19, 141, 155, 255}, {18, 144, 157, 255}, {18, 144, 158, 255}, {17, 145, 159, 255}, {17, 146, 161, 255}, {15, 146, 161, 255}, {17, 148, 163, 255}, {16, 149, 163, 255}, {16, 150, 165, 255}, {16, 151, 165, 255}, {14, 149, 165, 255}, {15, 151, 165, 255}, {14, 151, 166, 255}, {14, 152, 167, 255}, {14, 152, 168, 255}, {13, 152, 168, 255}, {15, 153, 168, 255}, {13, 153, 168, 255}, {13, 154, 169, 255}, {13, 154, 169, 255}, {13, 155, 170, 255}, {12, 154, 170, 255}, {13, 155, 171, 255}, {13, 156, 171, 255}, {13, 156, 171, 255}, {13, 155, 171, 255}, {12, 154, 171, 255}, {13, 156, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {13, 156, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {12, 155, 171, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {11, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {10, 155, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {9, 154, 170, 255}, {76, 148, 156, 255}, {65, 82, 91, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 89, 97, 246}, {71, 88, 96, 61}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 83, 91, 95}, {66, 81, 89, 254}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {75, 127, 134, 255}, {74, 150, 156, 255}, {75, 153, 161, 255}, {75, 153, 161, 255}, {75, 153, 162, 255}, {75, 153, 162, 255}, {74, 154, 162, 255}, {75, 155, 163, 255}, {75, 155, 163, 255}, {74, 155, 163, 255}, {74, 155, 164, 255}, {74, 155, 163, 255}, {75, 155, 164, 255}, {73, 155, 163, 255}, {74, 156, 164, 255}, {75, 156, 165, 255}, {74, 156, 164, 255}, {74, 156, 164, 255}, {74, 156, 164, 255}, {74, 157, 165, 255}, {75, 156, 166, 255}, {75, 158, 166, 255}, {73, 158, 165, 255}, {74, 158, 165, 255}, {74, 158, 166, 255}, {74, 158, 166, 255}, {74, 158, 166, 255}, {73, 157, 166, 255}, {74, 158, 166, 255}, {74, 158, 166, 255}, {74, 158, 166, 255}, {74, 158, 166, 255}, {73, 157, 166, 255}, {73, 158, 167, 255}, {73, 158, 167, 255}, {73, 158, 167, 255}, {74, 160, 167, 255}, {74, 160, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {73, 159, 167, 255}, {77, 165, 173, 255}, {77, 165, 174, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {77, 165, 174, 255}, {77, 165, 174, 255}, {77, 165, 173, 255}, {77, 165, 174, 255}, {77, 165, 174, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {77, 165, 174, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {76, 164, 173, 255}, {77, 165, 173, 255}, {77, 165, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {77, 165, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {75, 164, 173, 255}, {75, 164, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {75, 164, 173, 255}, {76, 164, 173, 255}, {76, 164, 173, 255}, {75, 164, 173, 255}, {75, 164, 173, 255}, {75, 164, 173, 255}, {76, 164, 173, 255}, {75, 164, 173, 255}, {76, 158, 167, 255}, {73, 126, 134, 255}, {67, 85, 93, 255}, {69, 89, 96, 255}, {69, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {68, 89, 97, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 86, 96, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {68, 87, 97, 255}, {69, 88, 97, 255}, {67, 87, 96, 255}, {68, 87, 96, 255}, {69, 89, 96, 252}, {68, 87, 97, 79}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 85, 90, 93}, {66, 82, 90, 250}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {71, 89, 97, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {72, 91, 101, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 89, 97, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {68, 88, 97, 255}, {69, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 88, 98, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {69, 87, 97, 255}, {68, 87, 97, 255}, {68, 88, 97, 246}, {70, 89, 99, 80}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {69, 83, 90, 74}, {67, 82, 89, 244}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 88, 255}, {75, 95, 104, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 94, 104, 255}, {70, 90, 97, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {70, 89, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {68, 88, 97, 255}, {69, 87, 98, 239}, {71, 88, 96, 61}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 82, 87, 56}, {67, 82, 89, 236}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {69, 85, 94, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {73, 92, 102, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {68, 88, 97, 255}, {69, 89, 97, 255}, {68, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {69, 88, 97, 255}, {68, 88, 96, 255}, {68, 87, 97, 255}, {68, 88, 98, 229}, {74, 91, 102, 45}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {68, 81, 93, 41}, {67, 82, 90, 225}, {66, 81, 89, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {74, 93, 102, 255}, {77, 97, 107, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {70, 90, 98, 255}, {69, 88, 96, 255}, {69, 88, 96, 255}, {69, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {69, 89, 98, 255}, {69, 88, 97, 255}, {68, 87, 97, 255}, {68, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 97, 255}, {70, 90, 98, 216}, {66, 90, 99, 31}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 82, 91, 28}, {67, 81, 90, 213}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {67, 82, 91, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 93, 104, 255}, {69, 88, 97, 255}, {68, 88, 97, 255}, {69, 87, 96, 255}, {68, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 86, 96, 255}, {69, 87, 97, 255}, {69, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 96, 255}, {69, 87, 96, 255}, {68, 88, 96, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 88, 97, 203}, {64, 89, 102, 20}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {60, 75, 90, 17}, {67, 81, 89, 198}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {72, 90, 99, 255}, {77, 97, 107, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {71, 90, 100, 255}, {69, 89, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {69, 88, 99, 185}, {70, 93, 116, 11}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {57, 85, 85, 9}, {66, 82, 89, 181}, {65, 81, 88, 255}, {66, 81, 89, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {65, 80, 88, 255}, {77, 96, 105, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {73, 94, 103, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {69, 89, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 88, 96, 255}, {68, 87, 97, 255}, {68, 88, 98, 166}, {102, 102, 102, 5}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 3}, {67, 82, 90, 153}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {70, 86, 96, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {71, 92, 100, 255}, {69, 88, 97, 255}, {68, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 86, 96, 255}, {69, 87, 97, 255}, {69, 88, 96, 255}, {68, 88, 96, 255}, {67, 87, 96, 255}, {68, 87, 97, 254}, {71, 89, 99, 137}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 81, 90, 91}, {67, 82, 89, 242}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {75, 94, 103, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {70, 89, 98, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {68, 86, 96, 255}, {69, 88, 98, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 86, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 87, 96, 255}, {70, 89, 97, 237}, {70, 89, 96, 77}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {65, 85, 92, 39}, {66, 81, 89, 209}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {68, 84, 93, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {72, 93, 102, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 87, 96, 255}, {68, 88, 96, 255}, {70, 89, 98, 198}, {70, 88, 97, 29}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {65, 80, 88, 153}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {72, 91, 99, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {70, 89, 99, 255}, {69, 88, 97, 255}, {68, 86, 96, 255}, {68, 86, 96, 255}, {69, 88, 98, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {69, 87, 97, 255}, {69, 88, 96, 255}, {68, 87, 96, 254}, {68, 89, 98, 138}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {66, 80, 89, 89}, {67, 83, 89, 241}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {65, 81, 89, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {72, 94, 102, 255}, {69, 87, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 86, 96, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {70, 88, 98, 236}, {70, 91, 97, 76}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 78, 85, 36}, {67, 83, 89, 207}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {69, 88, 96, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {77, 97, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {71, 91, 100, 255}, {68, 86, 96, 255}, {68, 86, 96, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {70, 88, 98, 197}, {73, 91, 100, 28}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 109, 7}, {64, 81, 88, 151}, {65, 81, 88, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 79, 86, 255}, {75, 95, 104, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 93, 103, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 87, 96, 255}, {69, 87, 97, 254}, {69, 88, 99, 136}, {128, 128, 128, 4}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {65, 81, 87, 79}, {66, 82, 88, 225}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 81, 88, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {68, 85, 94, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {72, 92, 100, 255}, {68, 86, 96, 255}, {69, 88, 98, 255}, {69, 87, 97, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {69, 87, 96, 255}, {67, 87, 95, 255}, {68, 88, 98, 217}, {68, 86, 98, 68}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {77, 77, 102, 10}, {65, 80, 87, 137}, {66, 81, 89, 249}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {74, 92, 101, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {69, 88, 98, 255}, {68, 87, 96, 255}, {67, 87, 95, 255}, {68, 87, 96, 255}, {68, 87, 96, 255}, {68, 88, 96, 246}, {69, 89, 97, 126}, {85, 85, 128, 6}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 85, 91, 42}, {65, 80, 88, 191}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {65, 82, 90, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {73, 93, 102, 255}, {68, 87, 96, 255}, {69, 88, 97, 255}, {68, 87, 96, 255}, {69, 88, 96, 180}, {70, 85, 93, 33}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {67, 80, 89, 92}, {65, 81, 88, 231}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {70, 90, 97, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {70, 90, 99, 255}, {69, 88, 97, 224}, {70, 89, 99, 80}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {73, 73, 91, 14}, {66, 80, 87, 147}, {66, 81, 88, 252}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {64, 80, 87, 255}, {76, 96, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {75, 94, 104, 249}, {74, 95, 107, 134}, {77, 102, 102, 10}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {68, 83, 88, 49}, {67, 81, 90, 199}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {68, 86, 94, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 96, 105, 189}, {77, 96, 108, 40}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {65, 82, 88, 90}, {65, 80, 89, 216}, {64, 79, 87, 255}, {64, 79, 87, 255}, {65, 80, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {63, 78, 86, 255}, {74, 93, 103, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 105, 209}, {76, 94, 104, 81}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 3}, {66, 81, 89, 97}, {65, 81, 89, 221}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {67, 83, 92, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {76, 96, 106, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 96, 105, 213}, {73, 94, 106, 87}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 128, 128, 4}, {64, 79, 87, 103}, {64, 80, 88, 223}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {73, 91, 101, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {75, 95, 106, 215}, {75, 97, 105, 92}, {128, 128, 128, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {85, 85, 85, 3}, {64, 79, 88, 87}, {66, 80, 87, 202}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {64, 80, 89, 255}, {75, 94, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {75, 96, 105, 194}, {74, 97, 107, 79}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {62, 78, 86, 62}, {63, 80, 87, 178}, {65, 80, 88, 253}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {69, 87, 97, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {75, 94, 104, 252}, {75, 95, 104, 169}, {76, 94, 104, 54}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {66, 80, 87, 35}, {64, 81, 88, 136}, {64, 80, 88, 230}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {63, 78, 87, 255}, {73, 94, 102, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {75, 95, 105, 225}, {75, 95, 105, 129}, {70, 97, 106, 29}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 128, 128, 4}, {65, 81, 87, 82}, {63, 80, 87, 182}, {65, 80, 88, 252}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {64, 79, 87, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {66, 85, 92, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 105, 251}, {76, 95, 105, 175}, {75, 95, 105, 75}, {128, 128, 128, 2}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 82, 91, 28}, {64, 79, 88, 116}, {64, 80, 87, 202}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 78, 86, 255}, {72, 92, 100, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 254}, {75, 96, 105, 196}, {75, 96, 105, 109}, {78, 100, 111, 23}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {62, 77, 85, 33}, {65, 80, 89, 121}, {63, 78, 87, 205}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {64, 82, 90, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 254}, {76, 96, 105, 199}, {76, 96, 105, 114}, {76, 94, 104, 27}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {68, 85, 85, 30}, {63, 80, 87, 105}, {63, 79, 88, 177}, {63, 79, 86, 242}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {69, 89, 98, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {75, 95, 105, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {75, 96, 106, 239}, {75, 94, 104, 171}, {75, 96, 107, 98}, {71, 92, 102, 25}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {102, 102, 102, 5}, {62, 77, 85, 66}, {65, 79, 87, 138}, {63, 79, 87, 199}, {62, 79, 86, 249}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {63, 78, 86, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 78, 86, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 94, 104, 255}, {74, 95, 104, 255}, {74, 96, 104, 247}, {76, 97, 106, 195}, {77, 98, 105, 133}, {72, 94, 106, 60}, {85, 170, 170, 3}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {64, 96, 96, 8}, {62, 78, 86, 62}, {63, 80, 86, 121}, {62, 78, 86, 180}, {64, 80, 87, 234}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {68, 86, 95, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {74, 95, 104, 255}, {76, 96, 106, 231}, {76, 96, 106, 175}, {76, 96, 107, 117}, {76, 94, 103, 57}, {85, 128, 128, 6}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {61, 71, 82, 25}, {64, 78, 85, 72}, {65, 80, 86, 118}, {64, 79, 87, 164}, {63, 78, 87, 209}, {63, 78, 86, 247}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {61, 77, 84, 245}, {61, 77, 86, 205}, {73, 92, 104, 160}, {75, 95, 106, 115}, {75, 94, 105, 68}, {70, 93, 104, 22}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {61, 71, 82, 25}, {65, 78, 86, 59}, {64, 80, 86, 92}, {64, 78, 86, 124}, {63, 78, 86, 157}, {62, 78, 85, 182}, {63, 79, 86, 201}, {64, 78, 86, 220}, {63, 78, 86, 239}, {62, 77, 85, 254}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 78, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 255}, {61, 76, 84, 255}, {62, 77, 85, 255}, {62, 77, 85, 255}, {61, 76, 84, 254}, {63, 77, 86, 237}, {62, 77, 85, 219}, {62, 78, 86, 199}, {62, 77, 86, 181}, {61, 78, 86, 154}, {61, 77, 86, 122}, {63, 80, 86, 89}, {59, 77, 87, 56}, {58, 81, 81, 22}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {255, 255, 255, 1}, {85, 85, 85, 6}, {51, 77, 77, 10}, {70, 93, 93, 11}, {59, 78, 98, 13}, {68, 85, 85, 15}, {60, 75, 90, 17}, {57, 85, 85, 18}, {57, 85, 85, 18}, {64, 80, 96, 16}, {68, 85, 85, 15}, {59, 78, 78, 13}, {70, 93, 93, 11}, {57, 85, 85, 9}, {85, 85, 85, 6}, {255, 255, 255, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, - {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0} - } -}; diff --git a/cmd/vulkan_sample/main.cpp b/cmd/vulkan_sample/main.cpp index e397bb7cb6..5635fdea89 100644 --- a/cmd/vulkan_sample/main.cpp +++ b/cmd/vulkan_sample/main.cpp @@ -14,15 +14,14 @@ * limitations under the License. */ -#include #include +#include #include #include #include #include #include #include - #include #if _WIN32 #include @@ -30,11 +29,13 @@ #elif defined(__ANDROID__) #include #include + #include "android_native_app_glue.h" #define VK_USE_PLATFORM_ANDROID_KHR #elif defined(__linux__) #include #include + #include #define VK_USE_PLATFORM_XCB_KHR #endif @@ -48,7 +49,7 @@ namespace cube { } namespace icon { -#include "icon.h" +#include "tools/logo/logo_256.h" } const uint32_t vertex_shader[] = #include "vert.h" @@ -60,6 +61,126 @@ const uint32_t fragment_shader[] = const static VkFormat kDepthFormat = VK_FORMAT_D16_UNORM; +static const char* vk_result_to_string(VkResult result) { + switch (result) { + case VK_SUCCESS: + return "Command successfully completed"; + case VK_NOT_READY: + return "A fence or query has not yet completed"; + case VK_TIMEOUT: + return "A wait operation has not completed in the specified time"; + case VK_EVENT_SET: + return "An event is signaled"; + case VK_EVENT_RESET: + return "An event is unsignaled"; + case VK_INCOMPLETE: + return "A return array was too small for the result"; + case VK_SUBOPTIMAL_KHR: + return "A swapchain no longer matches the surface properties exactly, " + "but can still be used to present to the surface successfully."; +#ifdef VK_THREAD_IDLE_KHR + case VK_THREAD_IDLE_KHR: + return "A deferred operation is not complete but there is currently no " + "work for this thread to do at the time of this call."; +#endif +#ifdef VK_THREAD_DONE_KHR + case VK_THREAD_DONE_KHR: + return "A deferred operation is not complete but there is no work " + "remaining to assign to additional threads."; +#endif +#ifdef VK_OPERATION_DEFERRED_KHR + case VK_OPERATION_DEFERRED_KHR: + return "A deferred operation was requested and at least some of the work " + "was deferred."; +#endif +#ifdef VK_OPERATION_NOT_DEFERRED_KHR + case VK_OPERATION_NOT_DEFERRED_KHR: + return "A deferred operation was requested and no operations were " + "deferred."; +#endif +#ifdef VK_PIPELINE_COMPILE_REQUIRED_EXT + case VK_PIPELINE_COMPILE_REQUIRED_EXT: + return "A requested pipeline creation would have required compilation, " + "but the application requested compilation to not be performed."; +#endif + case VK_ERROR_OUT_OF_HOST_MEMORY: + return "A host memory allocation has failed."; + case VK_ERROR_OUT_OF_DEVICE_MEMORY: + return "A device memory allocation has failed."; + case VK_ERROR_INITIALIZATION_FAILED: + return "Initialization of an object could not be completed for " + "implementation-specific reasons."; + case VK_ERROR_DEVICE_LOST: + return "The logical or physical device has been lost. See Lost Device"; + case VK_ERROR_MEMORY_MAP_FAILED: + return "Mapping of a memory object has failed."; + case VK_ERROR_LAYER_NOT_PRESENT: + return "A requested layer is not present or could not be loaded."; + case VK_ERROR_EXTENSION_NOT_PRESENT: + return "A requested extension is not supported."; + case VK_ERROR_FEATURE_NOT_PRESENT: + return "A requested feature is not supported."; + case VK_ERROR_INCOMPATIBLE_DRIVER: + return "The requested version of Vulkan is not supported by the driver " + "or is otherwise incompatible for implementation-specific " + "reasons."; + case VK_ERROR_TOO_MANY_OBJECTS: + return "Too many objects of the type have already been created."; + case VK_ERROR_FORMAT_NOT_SUPPORTED: + return "A requested format is not supported on this device."; + case VK_ERROR_FRAGMENTED_POOL: + return "A pool allocation has failed due to fragmentation of the pool's " + "memory. This must only be returned if no attempt to allocate " + "host or device memory was made to accommodate the new " + "allocation. This should be returned in preference to " + "VK_ERROR_OUT_OF_POOL_MEMORY, but only if the implementation is " + "certain that the pool allocation failure was due to " + "fragmentation."; + case VK_ERROR_SURFACE_LOST_KHR: + return "A surface is no longer available."; + case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: + return "The requested window is already in use by Vulkan or another API " + "in a manner which prevents it from being used again."; + case VK_ERROR_OUT_OF_DATE_KHR: + return "A surface has changed in such a way that it is no longer " + "compatible with the swapchain, and further presentation requests " + "using the swapchain will fail. Applications must query the new " + "surface properties and recreate their swapchain if they wish to " + "continue presenting to the surface."; + case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: + return "The display used by a swapchain does not use the same " + "presentable image layout, or is incompatible in a way that " + "prevents sharing an image."; + case VK_ERROR_INVALID_SHADER_NV: + return "One or more shaders failed to compile or link. More details are " + "reported back to the application via VK_EXT_debug_report if " + "enabled."; + case VK_ERROR_OUT_OF_POOL_MEMORY: + return "A pool memory allocation has failed. This must only be returned " + "if no attempt to allocate host or device memory was made to " + "accommodate the new allocation. If the failure was definitely " + "due to fragmentation of the pool, VK_ERROR_FRAGMENTED_POOL " + "should be returned instead."; + case VK_ERROR_INVALID_EXTERNAL_HANDLE: + return "An external handle is not a valid handle of the specified type."; + case VK_ERROR_FRAGMENTATION: + return "A descriptor pool creation has failed due to fragmentation."; + case VK_ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS: + return "A buffer creation or memory allocation failed because the " + "requested address is not available. A shader group handle " + "assignment failed because the requested shader group handle " + "information is no longer valid."; + case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT: + return "An operation on a swapchain created with " + "VK_FULL_SCREEN_EXCLUSIVE_APPLICATION_CONTROLLED_EXT failed as it " + "did not have exlusive full-screen access. This may occur due to " + "implementation-dependent reasons, outside of the application's " + "control."; + default: + return "Unknown VkResult"; + } +} + #if _WIN32 HWND kNativeWindowHandle; HINSTANCE kNativeHInstance; @@ -141,7 +262,7 @@ void ProcessNativeWindowEvents() { const int kOutHandle = 0; void write_error(int, const char* message) { - __android_log_print(ANDROID_LOG_ERROR, "GAPIDVKSAMPLE", "%s", message); + __android_log_print(ANDROID_LOG_ERROR, "AGIVKSAMPLE", "%s", message); } const char* kRequiredInstanceExtensions[] = { @@ -423,14 +544,25 @@ int main(int argc, const char** argv) { dlsym(kVulkanLibraryHandle, "vkGetInstanceProcAddr")); #endif -#define REQUIRE_SUCCESS(fn) \ - do { \ - if (VK_SUCCESS != fn) { \ - write_error(kOutHandle, "Vulkan Error: " #fn); \ - return -1; \ - } \ +#define REQUIRE_RESULT(fn, ...) \ + do { \ + const VkResult valid[] = {__VA_ARGS__}; \ + const size_t size = sizeof(valid) / sizeof(valid[0]); \ + const VkResult res = fn; \ + bool ok = false; \ + for (size_t i = 0; i < size && !ok; ++i) { \ + ok = (res == valid[i]); \ + } \ + if (!ok) { \ + std::string msg = "Vulkan Error: " #fn ": "; \ + msg += vk_result_to_string(res); \ + write_error(kOutHandle, msg.data()); \ + return -1; \ + } \ } while (0) +#define REQUIRE_SUCCESS(fn) REQUIRE_RESULT(fn, VK_SUCCESS) + #define LOAD_INSTANCE_FUNCTION(name, instance) \ PFN_##name name = \ reinterpret_cast(vkGetInstanceProcAddr(instance, #name)); @@ -444,11 +576,13 @@ int main(int argc, const char** argv) { vkEnumerateInstanceExtensionProperties(nullptr, &nExtensions, nullptr)); uint32_t nRequiredExtensions = sizeof(kRequiredInstanceExtensions) / sizeof(kRequiredInstanceExtensions[0]); - + std::vector instance_extensions; { std::vector extension_properties(nExtensions); REQUIRE_SUCCESS(vkEnumerateInstanceExtensionProperties( nullptr, &nExtensions, extension_properties.data())); + + // Check and enable required extensions. for (uint32_t i = 0; i < nRequiredExtensions; ++i) { bool found = false; for (auto& prop : extension_properties) { @@ -458,6 +592,16 @@ int main(int argc, const char** argv) { } if (!found) { write_error(kOutHandle, "Could not find all instance extensions"); + } else { + instance_extensions.push_back(kRequiredInstanceExtensions[i]); + } + } + + // Enable optional extensions. + for (auto& prop : extension_properties) { + if (std::string(prop.extensionName) == + VK_EXT_DEBUG_UTILS_EXTENSION_NAME) { + instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } } } @@ -471,14 +615,15 @@ int main(int argc, const char** argv) { "sample_engine", 0, VK_MAKE_VERSION(1, 0, 0)}; - VkInstanceCreateInfo create_info{VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, - nullptr, - 0, - &app_info, - 0, - nullptr, - nRequiredExtensions, - kRequiredInstanceExtensions}; + VkInstanceCreateInfo create_info{ + VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, + nullptr, + 0, + &app_info, + 0, + nullptr, + static_cast(instance_extensions.size()), + instance_extensions.data()}; REQUIRE_SUCCESS(vkCreateInstance(&create_info, nullptr, &instance)); } @@ -618,6 +763,10 @@ int main(int argc, const char** argv) { } LOAD_INSTANCE_FUNCTION(vkGetDeviceProcAddr, instance) + + // This is part of VK_EXT_debug_utils + // which is actually an instance extension + LOAD_INSTANCE_FUNCTION(vkSetDebugUtilsObjectNameEXT, instance); #undef LOAD_INSTANCE_FUNCTION #define LOAD_DEVICE_FUNCTION(name) \ @@ -714,10 +863,33 @@ int main(int argc, const char** argv) { LOAD_DEVICE_FUNCTION(vkCreateCommandPool); LOAD_DEVICE_FUNCTION(vkAllocateCommandBuffers); LOAD_DEVICE_FUNCTION(vkCreateFramebuffer); - LOAD_DEVICE_FUNCTION(vkDestroyFramebuffer); LOAD_DEVICE_FUNCTION(vkAcquireNextImageKHR); LOAD_DEVICE_FUNCTION(vkQueuePresentKHR); #undef LOAD_DEVICE_FUNCTION + +#define SET_DEBUG_LABEL(handle, type, label) \ + do { \ + if (vkSetDebugUtilsObjectNameEXT) { \ + VkDebugUtilsObjectNameInfoEXT info{ \ + VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT, \ + nullptr, \ + type, \ + reinterpret_cast(handle), \ + label, \ + }; \ + vkSetDebugUtilsObjectNameEXT(device, &info); \ + } \ + } while (false) + + // Set debug labels of objects created before the device. + SET_DEBUG_LABEL(instance, VK_OBJECT_TYPE_INSTANCE, "KubieInstance"); + SET_DEBUG_LABEL(physical_device, VK_OBJECT_TYPE_PHYSICAL_DEVICE, + "KubiePhysDevice"); + SET_DEBUG_LABEL(device, VK_OBJECT_TYPE_DEVICE, "KubieDevice"); + SET_DEBUG_LABEL(queue, VK_OBJECT_TYPE_QUEUE, "KubieQueue"); + SET_DEBUG_LABEL(surface, VK_OBJECT_TYPE_SURFACE_KHR, "KubieSurface"); + SET_DEBUG_LABEL(swapchain, VK_OBJECT_TYPE_SWAPCHAIN_KHR, "KubieSwapchain"); + // Immutable Data VkBuffer vertex_buffer; VkDeviceMemory vertex_buffer_memory; @@ -751,11 +923,9 @@ int main(int argc, const char** argv) { VkSemaphore swapchain_image_ready_semaphores[kBufferingCount]; VkSemaphore render_done_semaphores[kBufferingCount]; - // Transient things: Framebuffer for now. - VkFramebuffer framebuffers[kBufferingCount] = {}; - // Per swapchain image mutableData; std::vector swapchain_views; + std::vector framebuffers; // per (swap image, buffer) // Create the vertex buffer && back it with memory { @@ -770,6 +940,7 @@ int main(int argc, const char** argv) { nullptr}; REQUIRE_SUCCESS( vkCreateBuffer(device, &create_info, nullptr, &vertex_buffer)); + SET_DEBUG_LABEL(vertex_buffer, VK_OBJECT_TYPE_BUFFER, "KubieVertices"); VkMemoryRequirements memory_requirements; vkGetBufferMemoryRequirements(device, vertex_buffer, &memory_requirements); @@ -788,6 +959,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkAllocateMemory(device, &allocate_info, nullptr, &vertex_buffer_memory)); + SET_DEBUG_LABEL(vertex_buffer_memory, VK_OBJECT_TYPE_DEVICE_MEMORY, + "KubieVerticesMem"); REQUIRE_SUCCESS( vkBindBufferMemory(device, vertex_buffer, vertex_buffer_memory, 0)); } @@ -805,6 +978,7 @@ int main(int argc, const char** argv) { nullptr}; REQUIRE_SUCCESS( vkCreateBuffer(device, &create_info, nullptr, &index_buffer)); + SET_DEBUG_LABEL(index_buffer, VK_OBJECT_TYPE_BUFFER, "KubieIndices"); VkMemoryRequirements memory_requirements; vkGetBufferMemoryRequirements(device, index_buffer, &memory_requirements); @@ -823,6 +997,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkAllocateMemory(device, &allocate_info, nullptr, &index_buffer_memory)); + SET_DEBUG_LABEL(index_buffer_memory, VK_OBJECT_TYPE_DEVICE_MEMORY, + "KubieIndicesMem"); REQUIRE_SUCCESS( vkBindBufferMemory(device, index_buffer, index_buffer_memory, 0)); } @@ -847,6 +1023,7 @@ int main(int argc, const char** argv) { VK_IMAGE_LAYOUT_UNDEFINED}; REQUIRE_SUCCESS(vkCreateImage(device, &create_info, nullptr, &texture)); + SET_DEBUG_LABEL(texture, VK_OBJECT_TYPE_IMAGE, "KubieTexture"); VkMemoryRequirements memory_requirements; vkGetImageMemoryRequirements(device, texture, &memory_requirements); @@ -865,6 +1042,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS( vkAllocateMemory(device, &allocate_info, nullptr, &texture_memory)); + SET_DEBUG_LABEL(texture_memory, VK_OBJECT_TYPE_DEVICE_MEMORY, + "KubieTextureMem"); REQUIRE_SUCCESS(vkBindImageMemory(device, texture, texture_memory, 0)); } @@ -888,6 +1067,7 @@ int main(int argc, const char** argv) { VK_BORDER_COLOR_FLOAT_TRANSPARENT_BLACK, false}; REQUIRE_SUCCESS(vkCreateSampler(device, &create_info, nullptr, &sampler)); + SET_DEBUG_LABEL(sampler, VK_OBJECT_TYPE_SAMPLER, "KubieSampler"); } { @@ -904,6 +1084,7 @@ int main(int argc, const char** argv) { VkImageSubresourceRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}}; REQUIRE_SUCCESS( vkCreateImageView(device, &create_info, nullptr, &image_view)); + SET_DEBUG_LABEL(image_view, VK_OBJECT_TYPE_IMAGE_VIEW, "KubieTextureView"); } { @@ -920,6 +1101,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkCreateDescriptorSetLayout(device, &create_info, nullptr, &descriptor_set_layout)); + SET_DEBUG_LABEL(descriptor_set_layout, VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT, + "KubieDescLayout"); } { @@ -933,6 +1116,8 @@ int main(int argc, const char** argv) { nullptr}; REQUIRE_SUCCESS(vkCreatePipelineLayout(device, &create_info, nullptr, &pipeline_layout)); + SET_DEBUG_LABEL(pipeline_layout, VK_OBJECT_TYPE_PIPELINE_LAYOUT, + "KubiePipelineLayout"); } { @@ -978,6 +1163,7 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS( vkCreateRenderPass(device, &create_info, nullptr, &render_pass)); + SET_DEBUG_LABEL(render_pass, VK_OBJECT_TYPE_RENDER_PASS, "KubieRenderPass"); } { @@ -987,6 +1173,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkCreateShaderModule(device, &create_info, nullptr, &vertex_shader_module)); + SET_DEBUG_LABEL(vertex_shader_module, VK_OBJECT_TYPE_SHADER_MODULE, + "KubieVertexShader"); } { @@ -996,6 +1184,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkCreateShaderModule(device, &create_info, nullptr, &fragment_shader_module)); + SET_DEBUG_LABEL(fragment_shader_module, VK_OBJECT_TYPE_SHADER_MODULE, + "KubieFragmentShader"); } { @@ -1143,6 +1333,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkCreateGraphicsPipelines( device, VK_NULL_HANDLE, 1, &create_info, nullptr, &graphics_pipeline)); + SET_DEBUG_LABEL(graphics_pipeline, VK_OBJECT_TYPE_PIPELINE, + "KubiePipeline"); } { @@ -1167,6 +1359,8 @@ int main(int argc, const char** argv) { sizes}; REQUIRE_SUCCESS(vkCreateDescriptorPool(device, &create_info, nullptr, &descriptor_pool)); + SET_DEBUG_LABEL(descriptor_pool, VK_OBJECT_TYPE_DESCRIPTOR_POOL, + "KubieDescPool"); } // Create the per-buffer resources @@ -1182,6 +1376,8 @@ int main(int argc, const char** argv) { nullptr}; REQUIRE_SUCCESS( vkCreateBuffer(device, &create_info, nullptr, &uniform_buffers[i])); + std::string name = "KubieUniformBuffer" + std::to_string(i); + SET_DEBUG_LABEL(uniform_buffers[i], VK_OBJECT_TYPE_BUFFER, name.data()); VkMemoryRequirements memory_requirements; vkGetBufferMemoryRequirements(device, uniform_buffers[i], @@ -1202,6 +1398,9 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkAllocateMemory(device, &allocate_info, nullptr, &uniform_buffer_memories[i])); + name += "Mem"; + SET_DEBUG_LABEL(uniform_buffer_memories[i], VK_OBJECT_TYPE_DEVICE_MEMORY, + name.data()); REQUIRE_SUCCESS(vkBindBufferMemory(device, uniform_buffers[i], uniform_buffer_memories[i], 0)); } @@ -1226,6 +1425,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS( vkCreateImage(device, &create_info, nullptr, &depth_buffers[i])); + std::string name = "KubieDepthBuffer" + std::to_string(i); + SET_DEBUG_LABEL(depth_buffers[i], VK_OBJECT_TYPE_IMAGE, name.data()); VkMemoryRequirements memory_requirements; vkGetImageMemoryRequirements(device, depth_buffers[i], @@ -1246,6 +1447,9 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkAllocateMemory(device, &allocate_info, nullptr, &depth_buffer_memories[i])); + name += "Mem"; + SET_DEBUG_LABEL(depth_buffer_memories[i], VK_OBJECT_TYPE_DEVICE_MEMORY, + name.data()); REQUIRE_SUCCESS(vkBindImageMemory(device, depth_buffers[i], depth_buffer_memories[i], 0)); } @@ -1263,6 +1467,9 @@ int main(int argc, const char** argv) { VkImageSubresourceRange{VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}}; REQUIRE_SUCCESS(vkCreateImageView(device, &create_info, nullptr, &depth_buffer_views[i])); + std::string name = "KubieDepthBuffer" + std::to_string(i) + "View"; + SET_DEBUG_LABEL(depth_buffer_views[i], VK_OBJECT_TYPE_IMAGE_VIEW, + name.data()); } { VkDescriptorSetAllocateInfo allocate_info{ @@ -1270,6 +1477,9 @@ int main(int argc, const char** argv) { descriptor_pool, 1, &descriptor_set_layout}; REQUIRE_SUCCESS(vkAllocateDescriptorSets(device, &allocate_info, &descriptor_sets[i])); + std::string name = "KubieDescSet" + std::to_string(i); + SET_DEBUG_LABEL(descriptor_sets[i], VK_OBJECT_TYPE_DESCRIPTOR_SET, + name.data()); VkDescriptorBufferInfo buffer_info{uniform_buffers[i], 0, VK_WHOLE_SIZE}; @@ -1310,22 +1520,29 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkCreateCommandPool(device, &create_info, nullptr, &command_pools[i])); + std::string name = "KubieCmdPool" + std::to_string(i); + SET_DEBUG_LABEL(command_pools[i], VK_OBJECT_TYPE_COMMAND_POOL, + name.data()); } { - VkCommandBuffer cbs[2]; VkCommandBufferAllocateInfo allocate_info{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, nullptr, - command_pools[i], VK_COMMAND_BUFFER_LEVEL_PRIMARY, 2}; - REQUIRE_SUCCESS(vkAllocateCommandBuffers(device, &allocate_info, cbs)); - render_command_buffers[i] = cbs[1]; + command_pools[i], VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1}; + REQUIRE_SUCCESS(vkAllocateCommandBuffers(device, &allocate_info, + &render_command_buffers[i])); + std::string name = "KubieRenderCmdBuffer" + std::to_string(i); + SET_DEBUG_LABEL(render_command_buffers[i], VK_OBJECT_TYPE_COMMAND_BUFFER, + name.data()); } { VkFenceCreateInfo create_info{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, - nullptr, 0}; + nullptr, VK_FENCE_CREATE_SIGNALED_BIT}; REQUIRE_SUCCESS( vkCreateFence(device, &create_info, nullptr, &ready_fences[i])); + std::string name = "KubieFence" + std::to_string(i); + SET_DEBUG_LABEL(ready_fences[i], VK_OBJECT_TYPE_FENCE, name.data()); } { @@ -1333,8 +1550,14 @@ int main(int argc, const char** argv) { nullptr, 0}; REQUIRE_SUCCESS(vkCreateSemaphore(device, &create_info, nullptr, &swapchain_image_ready_semaphores[i])); + std::string name = "KubieReadySem" + std::to_string(i); + SET_DEBUG_LABEL(swapchain_image_ready_semaphores[i], + VK_OBJECT_TYPE_SEMAPHORE, name.data()); REQUIRE_SUCCESS(vkCreateSemaphore(device, &create_info, nullptr, &render_done_semaphores[i])); + name = "KubieDoneSem" + std::to_string(i); + SET_DEBUG_LABEL(render_done_semaphores[i], VK_OBJECT_TYPE_SEMAPHORE, + name.data()); } } @@ -1357,6 +1580,8 @@ int main(int argc, const char** argv) { nullptr}; REQUIRE_SUCCESS( vkCreateBuffer(device, &create_info, nullptr, &staging_buffer)); + SET_DEBUG_LABEL(staging_buffer, VK_OBJECT_TYPE_BUFFER, + "KubieStagingBuffer"); VkMemoryRequirements memory_requirements; vkGetBufferMemoryRequirements(device, staging_buffer, @@ -1377,6 +1602,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkAllocateMemory(device, &allocate_info, nullptr, &staging_buffer_memory)); + SET_DEBUG_LABEL(staging_buffer_memory, VK_OBJECT_TYPE_DEVICE_MEMORY, + "KubieStagingBufferMem"); REQUIRE_SUCCESS( vkBindBufferMemory(device, staging_buffer, staging_buffer_memory, 0)); } @@ -1420,6 +1647,8 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkCreateCommandPool(device, &create_info, nullptr, &staging_command_pool)); + SET_DEBUG_LABEL(staging_command_pool, VK_OBJECT_TYPE_COMMAND_POOL, + "KubieStagingCmdPool"); } { @@ -1428,6 +1657,8 @@ int main(int argc, const char** argv) { staging_command_pool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1}; REQUIRE_SUCCESS(vkAllocateCommandBuffers(device, &allocate_info, &staging_command_buffer)); + SET_DEBUG_LABEL(staging_command_buffer, VK_OBJECT_TYPE_COMMAND_BUFFER, + "KubieStagingCmdBuffer"); } { @@ -1536,20 +1767,42 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS( vkCreateImageView(device, &create_info, nullptr, &swapchain_view)); swapchain_views.push_back(swapchain_view); + std::string name = "KubieSwapchainView" + std::to_string(i); + SET_DEBUG_LABEL(swapchain_view, VK_OBJECT_TYPE_IMAGE_VIEW, name.data()); + + for (size_t j = 0; j < kBufferingCount; j++) { + VkImageView views[2] = {swapchain_views[i], depth_buffer_views[j]}; + VkFramebufferCreateInfo create_info = { + VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + nullptr, + 0, + render_pass, + 2, + views, + surface_capabilities.currentExtent.width, + surface_capabilities.currentExtent.height, + 1}; + VkFramebuffer framebuffer; + REQUIRE_SUCCESS( + vkCreateFramebuffer(device, &create_info, nullptr, &framebuffer)); + std::string name = + "KubieFrameBuffer" + std::to_string(i) + "." + std::to_string(j); + SET_DEBUG_LABEL(framebuffer, VK_OBJECT_TYPE_FRAMEBUFFER, name.data()); + framebuffers.push_back(framebuffer); + } } - std::unordered_set seen_swapchain_images; - uint64_t frame_count = 0; + // Actually Start Rendering. float total_time = 0; auto last_frame_time = std::chrono::high_resolution_clock::now(); - // Actually Start Rendering? + uint64_t frame_parity = kBufferingCount - 1; while (true) { auto current_time = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed_time = current_time - last_frame_time; last_frame_time = current_time; total_time += elapsed_time.count(); ProcessNativeWindowEvents(); - uint64_t frame_parity = frame_count % kBufferingCount; + frame_parity = (frame_parity + 1) % kBufferingCount; uint32_t next_image = 0; @@ -1558,11 +1811,10 @@ int main(int argc, const char** argv) { swapchain_image_ready_semaphores[frame_parity], VK_NULL_HANDLE, &next_image)); - if (frame_count >= kBufferingCount) { - REQUIRE_SUCCESS(vkWaitForFences(device, 1, &ready_fences[frame_parity], - false, static_cast(-1))); - REQUIRE_SUCCESS(vkResetFences(device, 1, &ready_fences[frame_parity])); - } + // Known issue b/158792228 + REQUIRE_SUCCESS(vkWaitForFences(device, 1, &ready_fences[frame_parity], + false, static_cast(-1))); + REQUIRE_SUCCESS(vkResetFences(device, 1, &ready_fences[frame_parity])); VkCommandBufferBeginInfo begin_info{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, nullptr, @@ -1571,54 +1823,13 @@ int main(int argc, const char** argv) { REQUIRE_SUCCESS(vkBeginCommandBuffer(render_command_buffers[frame_parity], &begin_info)); - if (frame_count < kBufferingCount) { - // Pipeline barrier here to convert images into their initial format - VkImageMemoryBarrier image_barrier{ - VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, - nullptr, - 0, - VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, - VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, - queue_family_index, - queue_family_index, - depth_buffers[frame_parity], - VkImageSubresourceRange{VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}}; - - vkCmdPipelineBarrier(render_command_buffers[frame_parity], - VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, 0, 0, - nullptr, 0, nullptr, 1, &image_barrier); - } - - if (framebuffers[frame_parity] != VK_NULL_HANDLE) { - vkDestroyFramebuffer(device, framebuffers[frame_parity], nullptr); - // Fill the framebuffers - } - - { - VkImageView views[2] = {swapchain_views[next_image], - depth_buffer_views[frame_parity]}; - VkFramebufferCreateInfo create_info = { - VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, - nullptr, - 0, - render_pass, - 2, - views, - surface_capabilities.currentExtent.width, - surface_capabilities.currentExtent.height, - 1}; - REQUIRE_SUCCESS(vkCreateFramebuffer(device, &create_info, nullptr, - &framebuffers[frame_parity])); - } float angle = 3.14f * total_time; float ca = cosf(angle); float sa = sinf(angle); - float mat[16] = {1.0f, 0.0f, 0.0f, 0.0f, 0.0f, ca, sa, 0.0f, - 0.0f, -sa, ca, 0.0f, 0.0f, 0.0f, -3.0f, 1.0f}; + float mat[16] = {ca, sa * sa, -ca * sa, 0.0f, 0.0f, ca, sa, 0.0f, + sa, -ca * sa, ca * ca, 0.0f, 0.0f, 0.0f, -2.0f, 1.0f}; float aspect = float(surface_capabilities.currentExtent.width) / float(surface_capabilities.currentExtent.height); @@ -1689,7 +1900,7 @@ int main(int argc, const char** argv) { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, nullptr, render_pass, - framebuffers[frame_parity], + framebuffers[(next_image * kBufferingCount) + frame_parity], VkRect2D{{0, 0}, { surface_capabilities.currentExtent.width, @@ -1717,7 +1928,7 @@ int main(int argc, const char** argv) { vkCmdEndRenderPass(render_command_buffers[frame_parity]); REQUIRE_SUCCESS(vkEndCommandBuffer(render_command_buffers[frame_parity])); - VkPipelineStageFlags flags = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + VkPipelineStageFlags flags = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; VkSubmitInfo submit{VK_STRUCTURE_TYPE_SUBMIT_INFO, nullptr, @@ -1740,9 +1951,9 @@ int main(int argc, const char** argv) { &next_image, &result}; - REQUIRE_SUCCESS(vkQueuePresentKHR(queue, &present_info)); - REQUIRE_SUCCESS(result); - ++frame_count; + REQUIRE_RESULT(vkQueuePresentKHR(queue, &present_info), VK_SUCCESS, + VK_SUBOPTIMAL_KHR); + REQUIRE_RESULT(result, VK_SUCCESS, VK_SUBOPTIMAL_KHR); } return 0; diff --git a/core/app/BUILD.bazel b/core/app/BUILD.bazel index f825943ca1..85537a9a5c 100644 --- a/core/app/BUILD.bazel +++ b/core/app/BUILD.bazel @@ -13,7 +13,7 @@ # limitations under the License. load("@io_bazel_rules_go//go:def.bzl", "go_library") -load("//:version.bzl", "gapid_version") +load("//:version.bzl", "agi_version") go_library( name = "go_default_library", @@ -28,6 +28,7 @@ go_library( "oncrash.go", "profile.go", "run.go", + "stack.go", "verbs.go", ], importpath = "github.com/google/gapid/core/app", @@ -49,7 +50,7 @@ go_library( ], ) -gapid_version( +agi_version( name = "version", out = "default_version.go", template = "default_version.go.in", diff --git a/core/app/analytics/analytics.go b/core/app/analytics/analytics.go index 24231b033d..208705e0bb 100644 --- a/core/app/analytics/analytics.go +++ b/core/app/analytics/analytics.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics // Package analytics implements methods for sending GAPID usage data to // Google Analytics. @@ -29,7 +29,7 @@ import ( ) const ( - trackingID = "UA-106883190-2" + trackingID = "UA-106883190-3" ) var ( diff --git a/core/app/analytics/batcher.go b/core/app/analytics/batcher.go index f7bea4c034..f57967dbb0 100644 --- a/core/app/analytics/batcher.go +++ b/core/app/analytics/batcher.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics package analytics diff --git a/core/app/analytics/crash_reporter.go b/core/app/analytics/crash_reporter.go index f65754f259..14b2de8721 100644 --- a/core/app/analytics/crash_reporter.go +++ b/core/app/analytics/crash_reporter.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics package analytics diff --git a/core/app/analytics/encoder.go b/core/app/analytics/encoder.go index ee436435ab..9c443ff4f0 100644 --- a/core/app/analytics/encoder.go +++ b/core/app/analytics/encoder.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics package analytics diff --git a/core/app/analytics/endpoint.go b/core/app/analytics/endpoint.go index f7313709e7..9530f0ccb4 100644 --- a/core/app/analytics/endpoint.go +++ b/core/app/analytics/endpoint.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics package analytics diff --git a/core/app/analytics/helpers.go b/core/app/analytics/helpers.go index e4e774fad8..e67321caa0 100644 --- a/core/app/analytics/helpers.go +++ b/core/app/analytics/helpers.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics package analytics diff --git a/core/app/analytics/payloads.go b/core/app/analytics/payloads.go index 3d76e74b22..6bce818507 100644 --- a/core/app/analytics/payloads.go +++ b/core/app/analytics/payloads.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build analytics +//go:build analytics package analytics diff --git a/core/app/analytics/stubs.go b/core/app/analytics/stubs.go index 03957972a3..24ad5b55cb 100644 --- a/core/app/analytics/stubs.go +++ b/core/app/analytics/stubs.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build !analytics +//go:build !analytics package analytics diff --git a/core/app/auth/BUILD.bazel b/core/app/auth/BUILD.bazel index f4b72da986..4bec7b0499 100644 --- a/core/app/auth/BUILD.bazel +++ b/core/app/auth/BUILD.bazel @@ -35,6 +35,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["auth_test.go"], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/app/auth/auth.go b/core/app/auth/auth.go index 311ea0c172..c4bb4f6c96 100644 --- a/core/app/auth/auth.go +++ b/core/app/auth/auth.go @@ -91,9 +91,9 @@ func GenTokenFile() (path string, token Token) { return path, token } -// ServerInterceptor returns a grpc.UnaryServerInterceptor that checks incoming -// RPC calls for the given auth token. -func ServerInterceptor(token Token) grpc.UnaryServerInterceptor { +// UnaryServerInterceptor returns a grpc.UnaryServerInterceptor that checks +// incoming RPC calls for the given auth token. +func UnaryServerInterceptor(token Token) grpc.UnaryServerInterceptor { return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { if token != NoAuth { md, ok := metadata.FromIncomingContext(ctx) @@ -111,9 +111,29 @@ func ServerInterceptor(token Token) grpc.UnaryServerInterceptor { } } -// ClientInterceptor returns a grpc.UnaryClientInterceptor that adds the given -// auth token to outgoing RPC calls. -func ClientInterceptor(token Token) grpc.UnaryClientInterceptor { +// StreamServerInterceptor returns a grpc.StreamServerInterceptor that checks +// incoming RPC calls for the given auth token. +func StreamServerInterceptor(token Token) grpc.StreamServerInterceptor { + return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + if token != NoAuth { + md, ok := metadata.FromIncomingContext(ss.Context()) + if !ok { + return ErrInvalidToken + } + + got, ok := md[rpcHeader] + if !ok || len(got) != 1 || Token(got[0]) != token { + return ErrInvalidToken + } + } + + return handler(srv, ss) + } +} + +// UnaryClientInterceptor returns a grpc.UnaryClientInterceptor that adds the +// given auth token to outgoing RPC calls. +func UnaryClientInterceptor(token Token) grpc.UnaryClientInterceptor { return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { if token != NoAuth { if md, ok := metadata.FromOutgoingContext(ctx); ok { @@ -125,3 +145,18 @@ func ClientInterceptor(token Token) grpc.UnaryClientInterceptor { return invoker(ctx, method, req, reply, cc, opts...) } } + +// StreamClientInterceptor returns a grpc.StreamClientInterceptor that adds the +// given auth token to outgoing RPC calls. +func StreamClientInterceptor(token Token) grpc.StreamClientInterceptor { + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + if token != NoAuth { + if md, ok := metadata.FromOutgoingContext(ctx); ok { + ctx = metadata.NewOutgoingContext(ctx, metadata.Join(md, metadata.Pairs(rpcHeader, string(token)))) + } else { + ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs(rpcHeader, string(token))) + } + } + return streamer(ctx, desc, cc, method, opts...) + } +} diff --git a/core/app/benchmark/BUILD.bazel b/core/app/benchmark/BUILD.bazel index 008d7c8ea7..5ce59f70ed 100644 --- a/core/app/benchmark/BUILD.bazel +++ b/core/app/benchmark/BUILD.bazel @@ -33,6 +33,8 @@ go_test( "complexity_test.go", "counter_test.go", ], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/app/benchmark/complexity.go b/core/app/benchmark/complexity.go index 3971d576eb..91fea4eaff 100644 --- a/core/app/benchmark/complexity.go +++ b/core/app/benchmark/complexity.go @@ -39,8 +39,9 @@ func (linearTime) String() string { return "O(n)" } // Fit calculates simple linear regression // See https://en.wikipedia.org/wiki/Simple_linear_regression -// https://en.wikipedia.org/wiki/Covariance -// https://en.wikipedia.org/wiki/Variance +// +// https://en.wikipedia.org/wiki/Covariance +// https://en.wikipedia.org/wiki/Variance func (linearTime) Fit(samples Samples) (fit Fit, err float64) { if len(samples) < 2 { return nil, math.MaxFloat64 diff --git a/core/app/crash/reporting/reporting.go b/core/app/crash/reporting/reporting.go index 29d9af3191..9f08dd52a2 100644 --- a/core/app/crash/reporting/reporting.go +++ b/core/app/crash/reporting/reporting.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build crashreporting +//go:build crashreporting // Package reporting implements a crash reporter to send GAPID crashes to a // Google server. @@ -107,8 +107,11 @@ func (r Reporter) sendReport(body io.Reader, contentType, endpoint string) (stri if err == nil { defer res.Body.Close() } - if err != nil || res.StatusCode != http.StatusOK { - return "", fmt.Errorf("Failed to upload report request: %v (%v)", err, res.StatusCode) + if err != nil { + return "", fmt.Errorf("Failed to upload report request: %v", err) + } + if res.StatusCode != http.StatusOK { + return "", fmt.Errorf("Failed to upload report request: got HTTP status code %v", res.StatusCode) } buf := new(bytes.Buffer) diff --git a/core/app/crash/reporting/reporting_test.go b/core/app/crash/reporting/reporting_test.go index 0e3e0de530..706e857e58 100644 --- a/core/app/crash/reporting/reporting_test.go +++ b/core/app/crash/reporting/reporting_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build crashreporting +//go:build crashreporting package reporting diff --git a/core/app/crash/reporting/stubs.go b/core/app/crash/reporting/stubs.go index 333f9fe6d8..08e223cd4a 100644 --- a/core/app/crash/reporting/stubs.go +++ b/core/app/crash/reporting/stubs.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build !crashreporting +//go:build !crashreporting package reporting diff --git a/core/app/default_version.go.in b/core/app/default_version.go.in index c844470775..4fe0c0d316 100644 --- a/core/app/default_version.go.in +++ b/core/app/default_version.go.in @@ -16,9 +16,9 @@ package app func init() { Version = VersionSpec{ - Major: @GAPID_VERSION_MAJOR@, - Minor: @GAPID_VERSION_MINOR@, - Point: @GAPID_VERSION_POINT@, - Build: "@GAPID_BUILD_SHA@", + Major: @AGI_VERSION_MAJOR@, + Minor: @AGI_VERSION_MINOR@, + Point: @AGI_VERSION_POINT@, + Build: "@AGI_BUILD_SHA@", } } diff --git a/core/app/doc.go b/core/app/doc.go index 1e3e41f67c..b32c23d6c0 100644 --- a/core/app/doc.go +++ b/core/app/doc.go @@ -13,5 +13,4 @@ // limitations under the License. // Package app provides an opinionated common infrastructure to application structure. -// package app diff --git a/core/app/flags/BUILD.bazel b/core/app/flags/BUILD.bazel index 6107a92cdd..a642962fad 100644 --- a/core/app/flags/BUILD.bazel +++ b/core/app/flags/BUILD.bazel @@ -19,6 +19,7 @@ go_library( srcs = [ "choices.go", "doc.go", + "experimental.go", "flags.go", "repeated.go", "strings.go", @@ -34,6 +35,8 @@ go_test( "choices_test.go", "repeated_test.go", ], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/app/flags/experimental.go b/core/app/flags/experimental.go new file mode 100644 index 0000000000..48a4e1d82d --- /dev/null +++ b/core/app/flags/experimental.go @@ -0,0 +1,23 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +package flags + +// Experimental features are hidden behind the flags. All experimental feature flags must: +// 1. be named --experimental-enable- +// 2. be by default false/off +// 3. be removed once the feature is no longer in experiment +var ( +// None at this time. +) diff --git a/core/app/flags/flags.go b/core/app/flags/flags.go index f3f9162759..7da7b73bf7 100644 --- a/core/app/flags/flags.go +++ b/core/app/flags/flags.go @@ -272,7 +272,7 @@ func (s *Set) Args() []string { } // ForceCommandLine is an ugly hack for things that try to directly use the flag package -//TODO: remove this +// TODO: remove this func (s *Set) ForceCommandLine() { flag.CommandLine = &s.Raw } diff --git a/core/app/flags/repeated.go b/core/app/flags/repeated.go index a5494055a7..52d2546bf1 100644 --- a/core/app/flags/repeated.go +++ b/core/app/flags/repeated.go @@ -29,7 +29,7 @@ type repeated struct { } const ( - dummyFlag = "dummy" + phFlag = "placeholder" ) func newRepeatedFlag(value reflect.Value) flag.Value { @@ -38,28 +38,28 @@ func newRepeatedFlag(value reflect.Value) flag.Value { single := reflect.New(value.Type().Elem()) switch s := single.Interface().(type) { case *bool: - fs.BoolVar(s, dummyFlag, *s, "") + fs.BoolVar(s, phFlag, *s, "") case *int: - fs.IntVar(s, dummyFlag, *s, "") + fs.IntVar(s, phFlag, *s, "") case *int64: - fs.Int64Var(s, dummyFlag, *s, "") + fs.Int64Var(s, phFlag, *s, "") case *uint: - fs.UintVar(s, dummyFlag, *s, "") + fs.UintVar(s, phFlag, *s, "") case *uint64: - fs.Uint64Var(s, dummyFlag, *s, "") + fs.Uint64Var(s, phFlag, *s, "") case *float64: - fs.Float64Var(s, dummyFlag, *s, "") + fs.Float64Var(s, phFlag, *s, "") case *string: - fs.StringVar(s, dummyFlag, *s, "") + fs.StringVar(s, phFlag, *s, "") case *time.Duration: - fs.DurationVar(s, dummyFlag, *s, "") + fs.DurationVar(s, phFlag, *s, "") case flag.Value: - fs.Var(s, dummyFlag, "") + fs.Var(s, phFlag, "") default: panic(fmt.Sprintf("Unhandled flag type: %v", single.Type())) } - return &repeated{value, single, fs.Lookup(dummyFlag).Value} + return &repeated{value, single, fs.Lookup(phFlag).Value} } func (f *repeated) String() string { diff --git a/core/app/layout/layout.go b/core/app/layout/layout.go index 0ea99e7cc9..4f6783b001 100644 --- a/core/app/layout/layout.go +++ b/core/app/layout/layout.go @@ -40,6 +40,7 @@ const ( LibGraphicsSpy LibraryType = iota LibVirtualSwapChain LibCPUTiming + LibDebugMarker LibMemoryTracker ) @@ -78,12 +79,14 @@ var libTypeToName = map[LibraryType]string{ LibGraphicsSpy: "libgapii", LibVirtualSwapChain: "libVkLayer_VirtualSwapchain", LibCPUTiming: "libVkLayer_CPUTiming", + LibDebugMarker: "libVkLayer_DebugMarker", LibMemoryTracker: "libVkLayer_MemoryTracker", } var layerNameToLibType = map[string]LibraryType{ "VirtualSwapchain": LibVirtualSwapChain, "CPUTiming": LibCPUTiming, + "DebugMarker": LibDebugMarker, "MemoryTracker": LibMemoryTracker, } @@ -91,7 +94,7 @@ var dataSourceNameToLayerName = map[string]string{ "VirtualSwapchain": "VirtualSwapchain", "VulkanCPUTiming": "CPUTiming", "VulkanMemoryTracker": "MemoryTracker", - "VulkanAPI": "CPUTiming", + "VulkanAPI": "DebugMarker", } var libTypeToJson = map[LibraryType]string{ @@ -99,6 +102,7 @@ var libTypeToJson = map[LibraryType]string{ LibVirtualSwapChain: "VirtualSwapchainLayer.json", LibCPUTiming: "CPUTimingLayer.json", LibMemoryTracker: "MemoryTrackerLayer.json", + LibDebugMarker: "DebugMarker.json", } func withLibraryPlatformSuffix(lib string, os device.OSKind) string { @@ -156,6 +160,8 @@ func hostOS(ctx context.Context) device.OSKind { dev = device.Linux case "darwin": dev = device.OSX + case "darwin_arm64": + dev = device.OSX } return dev } @@ -175,16 +181,16 @@ func (l pkgLayout) Gapit(ctx context.Context) (file.Path, error) { } func osToDir(k device.OSKind) string { - if device.IsLinuxLike(k) { + switch k { + case device.Linux: return "linux" - } - if k == device.OSX { + case device.OSX: return "macos" - } - if k == device.Windows { + case device.Windows: return "windows" + default: + return "" } - return "" } func (l pkgLayout) Gapir(ctx context.Context, abi *device.ABI) (file.Path, error) { @@ -266,6 +272,7 @@ var libTypeToLibPath = map[LibraryType]string{ LibGraphicsSpy: "gapid/gapii/cc/libgapii", LibVirtualSwapChain: "gapid/core/vulkan/vk_virtual_swapchain/cc/libVkLayer_VirtualSwapchain", LibCPUTiming: "gapid/core/vulkan/vk_api_timing_layer/cc/libVkLayer_CPUTiming", + LibDebugMarker: "gapid/core/vulkan/vk_api_timing_layer/cc/libVkLayer_DebugMarker", LibMemoryTracker: "gapid/core/vulkan/vk_memory_tracker_layer/cc/libVkLayer_MemoryTracker", } @@ -273,6 +280,7 @@ var libTypeToJsonPath = map[LibraryType]string{ LibGraphicsSpy: "gapid/gapii/vulkan/vk_graphics_spy/cc/GraphicsSpyLayer.json", LibVirtualSwapChain: "gapid/core/vulkan/vk_virtual_swapchain/cc/VirtualSwapchainLayer.json", LibCPUTiming: "gapid/core/vulkan/vk_api_timing_layer/cc/CPUTimingLayer.json", + LibDebugMarker: "gapid/core/vulkan/vk_api_timing_layer/cc/DebugMarkerLayer.json", LibMemoryTracker: "gapid/core/vulkan/vk_memory_tracker_layer/cc/MemoryTrackerLayer.json", } diff --git a/core/app/linker/BUILD.bazel b/core/app/linker/BUILD.bazel index cbac3dcb25..7a600b1ff2 100644 --- a/core/app/linker/BUILD.bazel +++ b/core/app/linker/BUILD.bazel @@ -25,6 +25,7 @@ go_library( clinkopts = select({ "//tools/build:linux": ["-ldl"], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": [], }), # keep importpath = "github.com/google/gapid/core/app/linker", diff --git a/core/app/linker/linker_posix.go b/core/app/linker/linker_posix.go index fd7de2a5f8..61d1a9cf26 100644 --- a/core/app/linker/linker_posix.go +++ b/core/app/linker/linker_posix.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build !windows +//go:build !windows package linker diff --git a/core/app/linker/linker_windows.go b/core/app/linker/linker_windows.go index 45073f75f5..2d15e53539 100644 --- a/core/app/linker/linker_windows.go +++ b/core/app/linker/linker_windows.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build windows +//go:build windows package linker diff --git a/core/app/log.go b/core/app/log.go index 9985c6c0cb..11c6340093 100644 --- a/core/app/log.go +++ b/core/app/log.go @@ -16,8 +16,8 @@ package app import ( "context" + "fmt" "os" - "path/filepath" "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/file" @@ -64,24 +64,46 @@ func updateContext(ctx context.Context, flags *LogFlags) context.Context { if flags.Stacks { ctx = log.PutStacktracer(ctx, log.SeverityStacktracer(log.Error)) } + if flags.File != "" { - // Create the server logfile. - os.MkdirAll(filepath.Dir(flags.File), 0755) - file, err := os.Create(flags.File) - if err != nil { - panic(err) - } - log.I(ctx, "Logging to: %v", flags.File) - // Build the logging context - handler := flags.Style.Handler(func(s string, _ log.Severity) { - file.WriteString(s) - file.WriteString("\n") - }) - handler = log.OnClosed(handler, func() { file.Close() }) - handler = wrapHandler(handler) - if old, _ := LogHandler.SetTarget(handler, false); old != nil { - old.Close() + if file := createLogFile(ctx, flags); file != nil { + // Build the file logging context. + handler := flags.Style.Handler(func(s string, _ log.Severity) { + file.WriteString(s) + file.WriteString("\n") + }) + handler = log.OnClosed(handler, func() { file.Close() }) + handler = wrapHandler(handler) + if old, _ := LogHandler.SetTarget(handler, false); old != nil { + old.Close() + } } } return ctx } + +func createLogFile(ctx context.Context, flags *LogFlags) *os.File { + path := file.Abs(flags.File) + dir, name, ext := path.Smash() + + if name == "" { + name = "gapis" + ext = ".log" + path = dir.Join(name + ext) + } + + os.MkdirAll(dir.System(), 0755) + for i := 0; i < 10; i++ { + file, err := os.Create(path.System()) + if err == nil { + log.I(ctx, "Logging to: %v", path.System()) + return file + } + + // Try a different path next. + path = dir.Join(fmt.Sprintf("%s-%d%s", name, i, ext)) + } + + log.E(ctx, "Failed to create log file "+flags.File) + return nil +} diff --git a/core/app/run.go b/core/app/run.go index 17f0ac8686..bbaca07aa9 100644 --- a/core/app/run.go +++ b/core/app/run.go @@ -76,6 +76,16 @@ type VersionSpec struct { Build string } +// VersionSpecFromTag parses the version from a git tag name. +func VersionSpecFromTag(tag string) (VersionSpec, error) { + var version VersionSpec + numFields, _ := fmt.Sscanf(tag, "v%d.%d.%d-%s", &version.Major, &version.Minor, &version.Point, &version.Build) + if numFields < 3 { + return version, fmt.Errorf("Failed to parse version tag: %s", tag) + } + return version, nil +} + // IsValid reports true if the VersionSpec is valid, ie it has a Major version. func (v VersionSpec) IsValid() bool { return v.Major >= 0 @@ -99,6 +109,20 @@ func (v VersionSpec) GreaterThan(o VersionSpec) bool { } } +// GreaterThanDevVersion returns true if v is greater than o, respecting dev versions. +func (v VersionSpec) GreaterThanDevVersion(o VersionSpec) bool { + if v.GreaterThan(o) { + return true + } + if !v.Equal(o) { + return false + } + + // dev-releases are previews of the next release, so e.g. 1.2.3 is more recent than 1.2.3-dev-456 + vDev, oDev := v.GetDevVersion(), o.GetDevVersion() + return oDev >= 0 && (vDev < 0 || vDev > oDev) +} + // Equal returns true if v is equal to o, ignoring the Build field. func (v VersionSpec) Equal(o VersionSpec) bool { return (v.Major == o.Major) && (v.Minor == o.Minor) && (v.Point == o.Point) diff --git a/core/app/stack.go b/core/app/stack.go new file mode 100644 index 0000000000..d8283c13fc --- /dev/null +++ b/core/app/stack.go @@ -0,0 +1,42 @@ +// Copyright (C) 2020 Google Inc. +// +// 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. + +package app + +import ( + "fmt" + "os" + "os/signal" + "runtime" + "syscall" + + "github.com/google/gapid/core/app/crash" +) + +// Adds a signal handler for SIGQUIT to dump all go-routine stacks. +func init() { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGQUIT) + crash.Go(func() { + for { + <-sigchan + + buf := make([]byte, 64<<10) + buf = buf[:runtime.Stack(buf, true)] + fmt.Println("------------------ Stack Dump: ------------------") + fmt.Println(string(buf)) + fmt.Println("-------------------------------------------------") + } + }) +} diff --git a/core/app/status/logger.go b/core/app/status/logger.go index 61e0c25157..3c46910148 100644 --- a/core/app/status/logger.go +++ b/core/app/status/logger.go @@ -17,6 +17,7 @@ package status import ( "context" "runtime" + "sync" "time" "github.com/google/gapid/core/log" @@ -34,20 +35,29 @@ func RegisterLogger(progressUpdateFreq time.Duration) Unregister { } type statusLogger struct { - lastProgressUpdate map[*Task]time.Time - progressUpdateFreq time.Duration + lastProgressUpdate map[*Task]time.Time + lastProgressUpdateLock sync.Mutex + progressUpdateFreq time.Duration } func (l statusLogger) OnTaskStart(ctx context.Context, t *Task) { - log.I(ctx, "%v Started", t) + l.lastProgressUpdateLock.Lock() l.lastProgressUpdate[t] = time.Now() + l.lastProgressUpdateLock.Unlock() + log.I(ctx, "%v Started", t) } func (l statusLogger) OnTaskProgress(ctx context.Context, t *Task) { - if time.Since(l.lastProgressUpdate[t]) > l.progressUpdateFreq { - log.I(ctx, "%v (%v%%) %v", t, t.Completion(), t.TimeSinceStart()) + l.lastProgressUpdateLock.Lock() + update := time.Since(l.lastProgressUpdate[t]) > l.progressUpdateFreq + if update { l.lastProgressUpdate[t] = time.Now() } + l.lastProgressUpdateLock.Unlock() + + if update { + log.I(ctx, "%v (%v%%) %v", t, t.Completion(), t.TimeSinceStart()) + } } func (l statusLogger) OnTaskBlock(ctx context.Context, t *Task) { @@ -60,7 +70,9 @@ func (l statusLogger) OnTaskUnblock(ctx context.Context, t *Task) { func (l statusLogger) OnTaskFinish(ctx context.Context, t *Task) { log.I(ctx, "%v Finished in %v", t, t.TimeSinceStart()) + l.lastProgressUpdateLock.Lock() delete(l.lastProgressUpdate, t) + l.lastProgressUpdateLock.Unlock() } func (l statusLogger) OnEvent(ctx context.Context, t *Task, n string, s EventScope) { diff --git a/core/assert/BUILD.bazel b/core/assert/BUILD.bazel index df503c0a79..ebab5253f5 100644 --- a/core/assert/BUILD.bazel +++ b/core/assert/BUILD.bazel @@ -55,5 +55,5 @@ go_test( "time_test.go", "value_test.go", ], - embed = [":go_default_library"], + deps = [":go_default_library"], ) diff --git a/core/assert/assertion.go b/core/assert/assertion.go index 9810d54224..0f9015e553 100644 --- a/core/assert/assertion.go +++ b/core/assert/assertion.go @@ -246,7 +246,8 @@ func (a *Assertion) TestDeepDiff(value, expect interface{}) bool { } // TestCustomDeepDiff is equivalent to TestCustomDeepEqual except it also prints -// a diff. +// +// a diff. func (a *Assertion) TestCustomDeepDiff(value, expect interface{}, c compare.Custom) bool { diff := c.Diff(value, expect, 10) if len(diff) == 0 { diff --git a/core/cc/BUILD.bazel b/core/cc/BUILD.bazel index fde4ab418a..4827587cf1 100644 --- a/core/cc/BUILD.bazel +++ b/core/cc/BUILD.bazel @@ -12,16 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("//:version.bzl", "gapid_version") +load("//:version.bzl", "agi_version") load("//tools/build:rules.bzl", "cc_copts") cc_library( name = "cc", srcs = glob( - [ - "*.cpp", - "gl/*.cpp", - ], + ["*.cpp"], exclude = [ "*_test.cpp", ], @@ -30,10 +27,19 @@ cc_library( "linux/*.cpp", "posix/*.cpp", ]), + "//tools/build:fuchsia-arm64": glob([ + "fuchsia/*.cpp", + "fuchsia/*.h", + "posix/*.cpp", + ]), "//tools/build:darwin": glob([ "osx/*.cpp", "posix/*.cpp", ]), + "//tools/build:darwin_arm64": glob([ + "osx/*.cpp", + "posix/*.cpp", + ]), "//tools/build:windows": glob(["windows/*.cpp"]), "//conditions:default": glob([ "android/*.cpp", @@ -41,14 +47,13 @@ cc_library( "posix/*.cpp", ]), }), - hdrs = glob([ - "*.h", - "gl/*.h", - ]), + hdrs = glob(["*.h"]), copts = cc_copts(), linkopts = select({ "//tools/build:linux": ["-ldl"], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], + "//tools/build:fuchsia-arm64": [], "//tools/build:windows": [ "-lws2_32", "-lshlwapi", @@ -58,12 +63,15 @@ cc_library( }), visibility = ["//visibility:public"], deps = [ - "@breakpad", "@cityhash", - ], + ] + select({ + # Turn off breakpad dependency on Fuchsia. + "//tools/build:fuchsia-arm64": [], + "//conditions:default": ["@breakpad"], + }), ) -gapid_version( +agi_version( name = "version", out = "version.h", template = "version.h.in", diff --git a/core/cc/android/get_gles_proc_address.cpp b/core/cc/android/get_gles_proc_address.cpp deleted file mode 100644 index 96ed6729ee..0000000000 --- a/core/cc/android/get_gles_proc_address.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "../get_gles_proc_address.h" -#include "../dl_loader.h" -#include "../log.h" - -#include -#include - -#if defined(__LP64__) -#define SYSTEM_LIB_PATH "/system/lib64/" -#else -#define SYSTEM_LIB_PATH "/system/lib/" -#endif - -namespace { - -void* ResolveSymbol(const char* name) { - using namespace core; - typedef void* (*GPAPROC)(const char* name); - - static DlLoader libegl(SYSTEM_LIB_PATH "libEGL.so"); - if (void* proc = libegl.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> %p (from libEGL dlsym)", name, proc); - return proc; - } - - static DlLoader libglesv2(SYSTEM_LIB_PATH "libGLESv2.so"); - if (void* proc = libglesv2.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> %p (from libGLESv2 dlsym)", name, - proc); - return proc; - } - - static DlLoader libglesv1(SYSTEM_LIB_PATH "libGLESv1_CM.so"); - if (void* proc = libglesv1.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> %p (from libGLESv1_CM dlsym)", name, - proc); - return proc; - } - - if (GPAPROC gpa = - reinterpret_cast(libegl.lookup("eglGetProcAddress"))) { - if (void* proc = gpa(name)) { - static DlLoader local(nullptr); - void* local_proc = local.lookup(name); - if (local_proc == proc) { - GAPID_WARNING( - "libEGL eglGetProcAddress returned a local address %p for %s, this " - "will be ignored", - proc, name); - } else { - GAPID_DEBUG( - "GetGlesProcAddress(%s) -> %p (via libEGL eglGetProcAddress)", name, - proc); - return proc; - } - } - } - - GAPID_DEBUG("GetGlesProcAddress(%s) -> not found", name); - return nullptr; -} - -void* getGlesProcAddress(const char* name) { - static std::unordered_map cache; - - auto it = cache.find(name); - if (it != cache.end()) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> %p (from cache)", name, it->second); - return it->second; - } - - void* proc = ResolveSymbol(name); - cache[name] = proc; - return proc; -} - -} // anonymous namespace - -namespace core { - -GetGlesProcAddressFunc* GetGlesProcAddress = getGlesProcAddress; -bool hasGLorGLES() { - return DlLoader::can_load(SYSTEM_LIB_PATH "libEGL.so") || - DlLoader::can_load(SYSTEM_LIB_PATH "libGLESv2.so") || - DlLoader::can_load(SYSTEM_LIB_PATH "libGLESv1_CM.so"); -} - -} // namespace core diff --git a/core/cc/android/process_name.cpp b/core/cc/android/process_name.cpp index 58e63b3d4c..e360cf95ee 100644 --- a/core/cc/android/process_name.cpp +++ b/core/cc/android/process_name.cpp @@ -24,8 +24,15 @@ static const size_t MAX_PATH = 4096; std::string get_process_name() { std::ifstream t("/proc/self/cmdline"); - return std::string((std::istreambuf_iterator(t)), - std::istreambuf_iterator()); + std::string name; + // Watch out: the string returned by reading /proc/self/cmdline contains '\0' + // characters to delimit the command line arguments (see man proc), make sure + // to stop at the first '\0' to only extract the process name. + auto it = std::istreambuf_iterator(t); + while (it != std::istreambuf_iterator() && *it != '\0') { + name += *it++; + } + return name; } uint64_t get_process_id() { return static_cast(getpid()); } diff --git a/core/cc/armlinux/get_gles_proc_address.cpp b/core/cc/armlinux/get_gles_proc_address.cpp deleted file mode 100644 index cfd43dfa01..0000000000 --- a/core/cc/armlinux/get_gles_proc_address.cpp +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "../get_gles_proc_address.h" -#include "../dl_loader.h" -#include "../log.h" - -#include -#include - -#define SYSTEM_LIB_PATH "/usr/lib/" - -namespace { - -void* ResolveSymbol(const char* name) { - using namespace core; - typedef void* (*GPAPROC)(const char* name); - - static DlLoader libegl(SYSTEM_LIB_PATH "libEGL.so"); - if (void* proc = libegl.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from libEGL dlsym)", name, - proc); - return proc; - } - - static DlLoader libglesv2(SYSTEM_LIB_PATH "libGLESv2.so"); - if (void* proc = libglesv2.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from libGLESv2 dlsym)", name, - proc); - return proc; - } - - static DlLoader libglesv1(SYSTEM_LIB_PATH "libGLESv1_CM.so"); - if (void* proc = libglesv1.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from libGLESv1_CM dlsym)", - name, proc); - return proc; - } - - if (GPAPROC gpa = - reinterpret_cast(libegl.lookup("eglGetProcAddress"))) { - if (void* proc = gpa(name)) { - static DlLoader local(nullptr); - void* local_proc = local.lookup(name); - if (local_proc == proc) { - GAPID_WARNING( - "libEGL eglGetProcAddress returned a local address %p for %s, this " - "will be ignored", - proc, name); - } else { - GAPID_DEBUG( - "GetGlesProcAddress(%s) -> 0x%x (via libEGL eglGetProcAddress)", - name, proc); - return proc; - } - } - } - - GAPID_DEBUG("GetGlesProcAddress(%s) -> not found", name); - return nullptr; -} - -void* getGlesProcAddress(const char* name) { - static std::unordered_map cache; - - auto it = cache.find(name); - if (it != cache.end()) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from cache)", name, - it->second); - return it->second; - } - - void* proc = ResolveSymbol(name); - cache[name] = proc; - return proc; -} - -} // anonymous namespace - -namespace core { -bool hasGLorGLES() { - return DlLoader::can_load(SYSTEM_LIB_PATH "libEGL.so") || - DlLoader::can_load(SYSTEM_LIB_PATH "libGLESv2.so") || - DlLoader::can_load(SYSTEM_LIB_PATH SYSTEM_LIB_PATH "libGLESv1_CM.so"); -} - -GetGlesProcAddressFunc* GetGlesProcAddress = getGlesProcAddress; - -} // namespace core diff --git a/core/cc/dl_loader.cpp b/core/cc/dl_loader.cpp index f57df14c5a..4b08cf1d7e 100644 --- a/core/cc/dl_loader.cpp +++ b/core/cc/dl_loader.cpp @@ -58,7 +58,7 @@ void* load(const char* name, ConstCharPtrs... fallback_names) { res = dlopen(name, RTLD_NOW | RTLD_LOCAL | RTLD_FIRST); } ret = res; -#elif TARGET_OS == GAPID_OS_ANDROID +#elif TARGET_OS == GAPID_OS_ANDROID || TARGET_OS == GAPID_OS_FUCHSIA ret = dlopen(name, RTLD_NOW | RTLD_LOCAL); #else ret = dlopen(name, RTLD_LAZY | RTLD_DEEPBIND); diff --git a/core/cc/fuchsia/debugger.cpp b/core/cc/fuchsia/debugger.cpp new file mode 100644 index 0000000000..c63b3af0f4 --- /dev/null +++ b/core/cc/fuchsia/debugger.cpp @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 "../debugger.h" + +static volatile bool gIsDebuggerAttached = false; + +namespace core { + +void Debugger::waitForAttach() { + while (!isAttached()) { + } +} + +bool Debugger::isAttached() { return gIsDebuggerAttached; } + +} // namespace core diff --git a/core/cc/fuchsia/get_vulkan_proc_address.cpp b/core/cc/fuchsia/get_vulkan_proc_address.cpp new file mode 100644 index 0000000000..0cf5dc4ca6 --- /dev/null +++ b/core/cc/fuchsia/get_vulkan_proc_address.cpp @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 "../dl_loader.h" +#include "../get_vulkan_proc_address.h" +#include "../log.h" + +namespace { + +using namespace core; + +// Definitions from the Vulkan API. Should keep the same with those in +// vulkan_types.h. +typedef void* PFN_vkVoidFunction; +typedef size_t VkDevice; +typedef size_t VkInstance; + +void* getVulkanInstanceProcAddress(size_t instance, const char* name) { + typedef PFN_vkVoidFunction (*VPAPROC)(VkInstance instance, const char* name); + + static DlLoader dylib("libvulkan.so", "libvulkan.so.1"); + + if (VPAPROC vpa = + reinterpret_cast(dylib.lookup("vkGetInstanceProcAddr"))) { + if (void* proc = vpa(instance, name)) { + GAPID_DEBUG("GetVulkanInstanceProcAddress(0x%x, %s) -> 0x%x", instance, + name, proc); + return proc; + } + } + + GAPID_DEBUG("GetVulkanInstanceProcAddress(0x%x, %s) -> not found", instance, + name); + return nullptr; +} + +void* getVulkanDeviceProcAddress(size_t instance, size_t device, + const char* name) { + typedef PFN_vkVoidFunction (*VPAPROC)(VkDevice device, const char* name); + + if (auto vpa = reinterpret_cast( + getVulkanInstanceProcAddress(instance, "vkGetDeviceProcAddr"))) { + if (void* proc = vpa(device, name)) { + GAPID_DEBUG("GetVulkanDeviceProcAddress(0x%x, 0x%x, %s) -> 0x%x", + instance, device, name, proc); + return proc; + } + } + GAPID_DEBUG("GetVulkanDeviceProcAddress(0x%x, 0x%x, %s) -> not found", + instance, device, name); + return nullptr; +} + +void* getVulkanProcAddress(const char* name) { + auto ret = getVulkanInstanceProcAddress(0u, name); + return ret; +} + +} // anonymous namespace + +namespace core { + +GetVulkanInstanceProcAddressFunc* GetVulkanInstanceProcAddress = + getVulkanInstanceProcAddress; +GetVulkanDeviceProcAddressFunc* GetVulkanDeviceProcAddress = + getVulkanDeviceProcAddress; +GetVulkanProcAddressFunc* GetVulkanProcAddress = getVulkanProcAddress; +bool HasVulkanLoader() { + auto ret = DlLoader::can_load("libvulkan.dylib") || + DlLoader::can_load("libvulkan.1.dylib"); + return ret; +} +} // namespace core diff --git a/core/cc/fuchsia/process_name.cpp b/core/cc/fuchsia/process_name.cpp new file mode 100644 index 0000000000..f7bd3f6676 --- /dev/null +++ b/core/cc/fuchsia/process_name.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 "utils.h" + +#include + +#include "zircon/process.h" +#include "zircon/syscalls.h" +#include "zircon/syscalls/object.h" +#include "zircon/threads.h" + +namespace core { + +std::string get_process_name() { + char name[ZX_MAX_NAME_LEN]; + if (zx_object_get_property(zx_process_self(), ZX_PROP_NAME, name, + sizeof(name)) == ZX_OK) { + return std::string(name); + } + fprintf(stderr, "ERROR: Unable to get process name.\n"); + return std::string(); +} + +uint64_t get_process_id() { + uint64_t koid = 0; + if (KoidFromHandle(zx_process_self(), &koid)) { + return koid; + } + fprintf(stderr, "ERROR: Unable to get process id.\n"); + return 0; +} + +} // namespace core diff --git a/core/cc/fuchsia/thread.cpp b/core/cc/fuchsia/thread.cpp new file mode 100644 index 0000000000..a9b46eb497 --- /dev/null +++ b/core/cc/fuchsia/thread.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 "core/cc/thread.h" + +#include "zircon/syscalls.h" +#include "zircon/syscalls/object.h" +#include "zircon/threads.h" + +#include "utils.h" +namespace core { + +Thread Thread::current() { + uint64_t koid = 0; + if (KoidFromHandle(thrd_get_zx_handle(thrd_current()), &koid)) { + return Thread(koid); + } + fprintf(stderr, "ERROR: Unable to get current thread id.\n"); + return Thread(0); +} + +std::string Thread::get_name() const { + char name[ZX_MAX_NAME_LEN]; + zx_status_t status = zx_object_get_property( + thrd_get_zx_handle(thrd_current()), ZX_PROP_NAME, name, sizeof(name)); + if (ZX_OK == status) { + return std::string(name); + } + fprintf(stderr, "ERROR: Unable to get current thread name.\n"); + return std::string(); +} + +} // namespace core diff --git a/core/cc/fuchsia/utils.cpp b/core/cc/fuchsia/utils.cpp new file mode 100644 index 0000000000..e351d6b269 --- /dev/null +++ b/core/cc/fuchsia/utils.cpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 "utils.h" + +#include + +#include "zircon/process.h" +#include "zircon/syscalls.h" + +namespace core { + +bool KoidFromHandle(uint32_t handle, uint64_t* koid) { + zx_info_handle_basic_t info; + zx_status_t status = zx_object_get_info(handle, ZX_INFO_HANDLE_BASIC, &info, + sizeof(info), nullptr, nullptr); + if (status != ZX_OK) { + fprintf(stderr, "ERROR: KoidFromHandle failed.\n"); + return false; + } + + *koid = info.koid; + return true; +} + +} // namespace core diff --git a/core/cc/fuchsia/utils.h b/core/cc/fuchsia/utils.h new file mode 100644 index 0000000000..19e62970de --- /dev/null +++ b/core/cc/fuchsia/utils.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 + +namespace core { +bool KoidFromHandle(uint32_t handle, uint64_t* koid); +} // namespace core diff --git a/core/cc/get_gles_proc_address.h b/core/cc/get_gles_proc_address.h deleted file mode 100644 index 1c91274b3c..0000000000 --- a/core/cc/get_gles_proc_address.h +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ - -#ifndef CORE_GET_GLES_PROC_ADDRESS_H -#define CORE_GET_GLES_PROC_ADDRESS_H - -namespace core { - -typedef void*(GetGlesProcAddressFunc)(const char* name); - -// GetGlesProcAddress returns the GLES function pointer to the function with -// the given name, or nullptr if the function was not found. -extern GetGlesProcAddressFunc* GetGlesProcAddress; - -bool hasGLorGLES(); - -} // namespace core - -#endif // CORE_GET_GLES_PROC_ADDRESS_H diff --git a/core/cc/gl/formats.cpp b/core/cc/gl/formats.cpp deleted file mode 100644 index a2cb744e42..0000000000 --- a/core/cc/gl/formats.cpp +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "formats.h" - -#include "core/cc/log.h" - -namespace core { -namespace gl { - -bool getColorFormat(int r, int g, int b, int a, uint32_t& format) { - if (r == 8 && g == 8 && b == 8 && a == 8) { - format = GL_RGBA8; - return true; - } else if (r == 8 && g == 8 && b == 8 && a == 0) { - format = GL_RGB8; - return true; - } else if (r == 5 && g == 6 && b == 5 && a == 0) { - format = GL_RGB565; - return true; - } - return false; // Not a recognised combination. -} - -bool getColorBits(uint32_t format, int& r, int& g, int& b, int& a) { - switch (format) { - case GL_RGBA8: - r = 8; - g = 8; - b = 8; - a = 8; - return true; - case GL_RGB8: - r = 8; - g = 8; - b = 8; - a = 0; - return true; - case GL_RGB565: - r = 5; - g = 6; - b = 5; - a = 0; - return true; - } - return false; // Not a recognised combination. -} - -// See: -// https://www.khronos.org/opengles/sdk/docs/man3/docbook4/xhtml/glRenderbufferStorage.xml -bool getDepthStencilFormat(int d, int s, uint32_t& depth, uint32_t& stencil) { - depth = 0; - stencil = 0; - - if (d == 0 && s == 0) { - return true; // No depth, no stencil. - } - - switch (s) { - case 0: - switch (d) { - case 16: - depth = GL_DEPTH_COMPONENT16; - return true; - case 24: - depth = GL_DEPTH_COMPONENT24; - return true; - case 32: - depth = GL_DEPTH_COMPONENT32F; - return true; - } - break; - case 8: - switch (d) { - case 0: - stencil = GL_STENCIL_INDEX8; - return true; - case 24: - depth = GL_DEPTH24_STENCIL8; - stencil = GL_DEPTH24_STENCIL8; - return true; - case 32: - depth = GL_DEPTH32F_STENCIL8; - stencil = GL_DEPTH32F_STENCIL8; - return true; - } - break; - } - return false; // Not a recognised combination. -} - -bool getDepthBits(uint32_t format, int& d) { - d = 0; - switch (format) { - case 0: - return true; - case GL_DEPTH_COMPONENT16: - d = 16; - return true; - case GL_DEPTH_COMPONENT24: - case GL_DEPTH24_STENCIL8: - d = 24; - return true; - case GL_DEPTH_COMPONENT32F: - case GL_DEPTH32F_STENCIL8: - d = 32; - return true; - } - return false; // Not a recognised combination. -} - -bool getStencilBits(uint32_t format, int& s) { - s = 0; - switch (format) { - case 0: - return true; - case GL_STENCIL_INDEX8: - case GL_DEPTH24_STENCIL8: - case GL_DEPTH32F_STENCIL8: - s = 8; - return true; - } - return false; // Not a recognised combination. -} - -} // namespace gl -} // namespace core diff --git a/core/cc/gl/formats.h b/core/cc/gl/formats.h deleted file mode 100644 index 5d92b90bb5..0000000000 --- a/core/cc/gl/formats.h +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ - -#ifndef CORE_GL_FORMATS_H -#define CORE_GL_FORMATS_H - -#include - -namespace core { -namespace gl { - -const uint32_t GL_RGB8 = 0x00008051; -const uint32_t GL_RGBA8 = 0x00008058; -const uint32_t GL_RGB565 = 0x00008D62; -const uint32_t GL_DEPTH_COMPONENT16 = 0x000081A5; -const uint32_t GL_DEPTH_COMPONENT24 = 0x000081A6; -const uint32_t GL_DEPTH_COMPONENT32F = 0x00008CAC; -const uint32_t GL_DEPTH32F_STENCIL8 = 0x00008CAD; -const uint32_t GL_DEPTH24_STENCIL8 = 0x000088F0; -const uint32_t GL_STENCIL_INDEX8 = 0x00008D48; - -// getColorFormat gets the color buffer format given the number of bits for the -// red, green, blue and alpha channels. Returns true on success, or false if -// there is no format for the given bit combination. -bool getColorFormat(int r, int g, int b, int a, uint32_t& format); - -// getColorBits gets the number of bits for the red, green, blue and alpha -// channels for the given format. Returns true on success, or false if the -// format is not recognised. -bool getColorBits(uint32_t format, int& r, int& g, int& b, int& a); - -// getDepthStencilFormat gets the depth and stencil buffer formats given the -// number of bits for the depth and stencil channels. Returns true on success, -// or false if there is no format combination for the given bits. -bool getDepthStencilFormat(int d, int s, uint32_t& depth, uint32_t& stencil); - -// getDepthBits gets the number of bits for the depth channel for the given -// depth format. Returns true on success, or false if the format is not -// recognised. -bool getDepthBits(uint32_t format, int& d); - -// getStencilBits gets the number of bits for the stencil channel for the given -// stencil format. Returns true on success, or false if the format is not -// recognised. -bool getStencilBits(uint32_t format, int& s); - -} // namespace gl -} // namespace core - -#endif // CORE_GL_FORMATS_H diff --git a/core/cc/gl/versions.h b/core/cc/gl/versions.h deleted file mode 100644 index 8203b0e72c..0000000000 --- a/core/cc/gl/versions.h +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ - -#ifndef CORE_GL_VERSIONS_H -#define CORE_GL_VERSIONS_H - -namespace core { -namespace gl { - -// Version represents a single major.minor version of an OpenGL context. -struct Version { - int major; - int minor; -}; - -// sVersionSearchOrder is an preference-ordered list of OpenGL context versions -// that should be searched in order to pick the most recent version of OpenGL. -// The driver is always free to return a newer version, however some -// implementations will return the precise version requested, so if we request -// 3.2, we would get 3.2 even if a new version is available. -static const Version sVersionSearchOrder[] = { - // clang-format off - {4, 5}, // Compatible with OpenGL ES 3.1 - {4, 4}, - {4, 3}, // Compatible with OpenGL ES 3.0 - {4, 2}, - {4, 1}, // Compatible with OpenGL ES 2.0 - {4, 0}, - {3, 3}, - {3, 2}, // Introduces core profile - // clang-format on -}; - -} // namespace gl -} // namespace core - -#endif // CORE_GL_VERSIONS_H diff --git a/core/cc/id.h b/core/cc/id.h index a97c5f801b..e8a494f2f8 100644 --- a/core/cc/id.h +++ b/core/cc/id.h @@ -20,6 +20,7 @@ #include #include #include +#include namespace core { diff --git a/core/cc/linux/get_gles_proc_address.cpp b/core/cc/linux/get_gles_proc_address.cpp deleted file mode 100644 index 72d8616c45..0000000000 --- a/core/cc/linux/get_gles_proc_address.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "../get_gles_proc_address.h" -#include "../dl_loader.h" -#include "../log.h" - -#include - -namespace { - -// The mesa driver does bad things with LLVM. Since we also use llvm, -// we can't have the mesa driver do bad things to our code. -// Therefore we should preload any versions of llvm that may be required -// into the start of our address space. -// See: https://github.com/google/gapid/issues/1707 for more information -struct MesaLLVMOpener { - MesaLLVMOpener() { - char name[512]; - for (int i = 3; i <= 9; i++) { - snprintf(name, sizeof(name), "libLLVM-%d.0.so.1", i); - dlopen(name, RTLD_LAZY | RTLD_DEEPBIND); - snprintf(name, sizeof(name), "libLLVM-%d.so.1", i); - dlopen(name, RTLD_LAZY | RTLD_DEEPBIND); - } - } -}; - -void* getGlesProcAddress(const char* name) { - using namespace core; - typedef void* (*GPAPROC)(const char* name); - static MesaLLVMOpener _dummy; - (void)_dummy; - - // Why .1 ? - // See: https://bugs.launchpad.net/ubuntu/+source/python-qt4/+bug/941826 - static DlLoader libgl("libGL.so.1"); - if (GPAPROC gpa = - reinterpret_cast(libgl.lookup("glXGetProcAddress"))) { - if (void* proc = gpa(name)) { - GAPID_VERBOSE( - "GetGlesProcAddress(%s) -> 0x%x (via libGL glXGetProcAddress)", name, - proc); - return proc; - } - } - if (void* proc = libgl.lookup(name)) { - GAPID_VERBOSE("GetGlesProcAddress(%s) -> 0x%x (from libGL dlsym)", name, - proc); - return proc; - } - - GAPID_DEBUG("GetGlesProcAddress(%s) -> not found", name); - return nullptr; -} - -} // anonymous namespace - -namespace core { - -GetGlesProcAddressFunc* GetGlesProcAddress = getGlesProcAddress; -bool hasGLorGLES() { return DlLoader::can_load("libGL.so.1"); } - -} // namespace core diff --git a/core/cc/linux/get_vulkan_proc_address.cpp b/core/cc/linux/get_vulkan_proc_address.cpp index 55663b8cde..7d6ab73f34 100644 --- a/core/cc/linux/get_vulkan_proc_address.cpp +++ b/core/cc/linux/get_vulkan_proc_address.cpp @@ -18,6 +18,8 @@ #include "../dl_loader.h" #include "../log.h" +#include + namespace { using namespace core; @@ -28,9 +30,29 @@ typedef void* PFN_vkVoidFunction; typedef size_t VkDevice; typedef size_t VkInstance; +// The mesa driver does bad things with LLVM. Since we also use llvm, +// we can't have the mesa driver do bad things to our code. +// Therefore we should preload any versions of llvm that may be required +// into the start of our address space. +// See: https://github.com/google/gapid/issues/1707 for more information +struct MesaLLVMOpener { + MesaLLVMOpener() { + char name[512]; + for (int i = 3; i <= 20; i++) { + snprintf(name, sizeof(name), "libLLVM-%d.0.so.1", i); + dlopen(name, RTLD_LAZY | RTLD_DEEPBIND); + snprintf(name, sizeof(name), "libLLVM-%d.so.1", i); + dlopen(name, RTLD_LAZY | RTLD_DEEPBIND); + } + } +}; + void* getVulkanInstanceProcAddress(size_t instance, const char* name) { typedef PFN_vkVoidFunction (*VPAPROC)(VkInstance instance, const char* name); + static MesaLLVMOpener _dlopenAllMesaVersions; + (void)_dlopenAllMesaVersions; + static DlLoader dylib("libvulkan.so", "libvulkan.so.1"); if (VPAPROC vpa = diff --git a/core/cc/make_unique.h b/core/cc/make_unique.h new file mode 100644 index 0000000000..0ed6d03192 --- /dev/null +++ b/core/cc/make_unique.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ + +#ifndef CORE_MAKE_UNIQUE_H +#define CORE_MAKE_UNIQUE_H + +#include + +#if __cplusplus > 201400 +using std::make_unique; +#else +template +std::unique_ptr make_unique(Args&&... args) { + return std::unique_ptr(new T(std::forward(args)...)); +} +#endif + +#endif // CORE_MAKE_UNIQUE_H diff --git a/core/cc/osx/debugger.cpp b/core/cc/osx/debugger.cpp index 736dc632cb..429be8f3d7 100644 --- a/core/cc/osx/debugger.cpp +++ b/core/cc/osx/debugger.cpp @@ -52,6 +52,7 @@ static bool AmIBeingDebugged(void) size = sizeof(info); junk = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); assert(junk == 0); + (void)junk; // We're being debugged if the P_TRACED flag is set. diff --git a/core/cc/osx/get_gles_proc_address.cpp b/core/cc/osx/get_gles_proc_address.cpp deleted file mode 100644 index da5c084caa..0000000000 --- a/core/cc/osx/get_gles_proc_address.cpp +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "../get_gles_proc_address.h" -#include "../dl_loader.h" -#include "../log.h" - -namespace { - -#define FRAMEWORK_ROOT "/System/Library/Frameworks/OpenGL.framework/" -#define CORE_GRAPHICS \ - "/System/Library/Frameworks/CoreGraphics.framework/CoreGraphics" - -void* getGlesProcAddress(const char* name) { - using namespace core; - static DlLoader opengl(FRAMEWORK_ROOT "OpenGL"); - if (void* proc = opengl.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from OpenGL dlsym)", name, - proc); - return proc; - } - - static DlLoader libgl(FRAMEWORK_ROOT "Libraries/libGL.dylib"); - if (void* proc = libgl.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from libGL dlsym)", name, - proc); - return proc; - } - - static DlLoader libglu(FRAMEWORK_ROOT "Libraries/libGLU.dylib"); - if (void* proc = libglu.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from libGLU dlsym)", name, - proc); - return proc; - } - - static DlLoader coregraphics(CORE_GRAPHICS); - if (void* proc = coregraphics.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from CoreGraphics dlsym)", - name, proc); - return proc; - } - - return nullptr; -} - -} // anonymous namespace - -namespace core { - -GetGlesProcAddressFunc* GetGlesProcAddress = getGlesProcAddress; -bool hasGLorGLES() { - return DlLoader::can_load(FRAMEWORK_ROOT "OpenGL") || - DlLoader::can_load(FRAMEWORK_ROOT "Libraries/libGL.dylib") || - DlLoader::can_load(FRAMEWORK_ROOT "Libraries/libGLU.dylib") || - DlLoader::can_load(CORE_GRAPHICS); -} - -} // namespace core diff --git a/core/cc/socket_connection.cpp b/core/cc/socket_connection.cpp index 145b50dcbf..3dfb6c3007 100644 --- a/core/cc/socket_connection.cpp +++ b/core/cc/socket_connection.cpp @@ -370,6 +370,7 @@ std::unique_ptr SocketConnection::createPipe(const char* pipename, } #endif + // Rely on zero-initialization here to assume pipe.sun_path is zeroed. struct sockaddr_un pipe {}; pipe.sun_family = AF_UNIX; strncpy(pipe.sun_path + (abstract ? 1 : 0), pipename, diff --git a/core/cc/target.h b/core/cc/target.h index 66e83d77af..4c3005cdd9 100644 --- a/core/cc/target.h +++ b/core/cc/target.h @@ -21,13 +21,15 @@ #define GAPID_OS_OSX 2 #define GAPID_OS_WINDOWS 3 #define GAPID_OS_ANDROID 4 +#define GAPID_OS_FUCHSIA 5 #define LINUX_ONLY(x) #define OSX_ONLY(x) #define WINDOWS_ONLY(x) #define ANDROID_ONLY(x) +#define FUCHSIA_ONLY(x) -#if defined(TARGET_OS_LINUX) +#if defined(GAPID_TARGET_OS_LINUX) #define TARGET_OS GAPID_OS_LINUX #define STDCALL #define EXPORT __attribute__((visibility("default"))) @@ -37,7 +39,17 @@ #define LINUX_ONLY(x) x #endif -#if defined(TARGET_OS_OSX) +#if defined(GAPID_TARGET_OS_FUCHSIA) +#define TARGET_OS GAPID_OS_FUCHSIA +#define STDCALL +#define EXPORT __attribute__((visibility("default"))) +#define PATH_DELIMITER '/' +#define PATH_DELIMITER_STR "/" +#undef FUCHSIA_ONLY +#define FUCHSIA_ONLY(x) x +#endif + +#if defined(GAPID_TARGET_OS_OSX) #define TARGET_OS GAPID_OS_OSX #define STDCALL #define EXPORT __attribute__((visibility("default"))) @@ -47,12 +59,12 @@ #define OSX_ONLY(x) x #include using size_val = uint64_t; -#else // defined(TARGET_OS_OSX) +#else // defined(GAPID_TARGET_OS_OSX) #include using size_val = size_t; #endif -#if defined(TARGET_OS_ANDROID) +#if defined(GAPID_TARGET_OS_ANDROID) #define TARGET_OS GAPID_OS_ANDROID #define STDCALL #define EXPORT __attribute__((visibility("default"))) @@ -62,7 +74,7 @@ using size_val = size_t; #define ANDROID_ONLY(x) x #endif -#if defined(TARGET_OS_WINDOWS) +#if defined(GAPID_TARGET_OS_WINDOWS) #define TARGET_OS GAPID_OS_WINDOWS #define STDCALL __stdcall #define EXPORT __declspec(dllexport) @@ -76,7 +88,7 @@ using size_val = size_t; #error "OS not defined correctly." #error \ "Exactly one of the following macro have to be defined:" \ - "TARGET_OS_LINUX, TARGET_OS_OSX, TARGET_OS_WINDOWS, TARGET_OS_ANDROID" + "GAPID_TARGET_OS_LINUX, GAPID_TARGET_OS_OSX, GAPID_TARGET_OS_WINDOWS, GAPID_TARGET_OS_ANDROID" #endif #ifdef _MSC_VER // MSVC diff --git a/core/cc/thread.h b/core/cc/thread.h index 820d6ce460..d6bd4cd638 100644 --- a/core/cc/thread.h +++ b/core/cc/thread.h @@ -19,6 +19,7 @@ #include #include +#include namespace core { diff --git a/core/cc/timer.cpp b/core/cc/timer.cpp index 3cd3149824..7827c047c2 100644 --- a/core/cc/timer.cpp +++ b/core/cc/timer.cpp @@ -32,13 +32,14 @@ namespace core { namespace { const uint64_t SEC_TO_NANO = 1000000000; -const uint64_t MICRO_TO_NANO = 1000; // Returns a monotonic clock reading in a platform-specific unit. // Use platformDurationToNanosecods() to convert the difference in values // returned from two calls to platformGetTime() into nanoseconds. inline uint64_t platformGetTime() { #if TARGET_OS == GAPID_OS_OSX + static const uint64_t MICRO_TO_NANO = 1000; + timeval tv = {0, 0}; if (gettimeofday(&tv, NULL) != 0) { GAPID_FATAL("Unable to start timer. Error: %d", errno); diff --git a/core/cc/version.h.in b/core/cc/version.h.in index 800288e242..fa28fd4b99 100644 --- a/core/cc/version.h.in +++ b/core/cc/version.h.in @@ -14,10 +14,10 @@ * limitations under the License. */ -#ifndef GAPID_VERSION_AND_BUILD -#define GAPID_VERSION_AND_BUILD \ - "@GAPID_VERSION_MAJOR@." \ - "@GAPID_VERSION_MINOR@." \ - "@GAPID_VERSION_POINT@." \ - "@GAPID_BUILD_SHA@" +#ifndef AGI_VERSION_AND_BUILD +#define AGI_VERSION_AND_BUILD \ + "@AGI_VERSION_MAJOR@." \ + "@AGI_VERSION_MINOR@." \ + "@AGI_VERSION_POINT@." \ + "@AGI_BUILD_SHA@" #endif diff --git a/core/cc/vulkan_ptr_types.h b/core/cc/vulkan_ptr_types.h index 49d71e1bb5..2af68cd0b4 100644 --- a/core/cc/vulkan_ptr_types.h +++ b/core/cc/vulkan_ptr_types.h @@ -36,12 +36,12 @@ #include "core/cc/target.h" -#if defined(TARGET_OS_WINDOWS) +#if TARGET_OS == GAPID_OS_WINDOWS // On Windows, Vulkan commands use the stdcall convention #define VKAPI_ATTR #define VKAPI_CALL __stdcall #define VKAPI_PTR VKAPI_CALL -#elif defined(TARGET_OS_ANDROID) && defined(__ARM_ARCH_7A__) +#elif TARGET_OS == GAPID_OS_ANDROID && defined(__ARM_ARCH_7A__) // On Android/ARMv7a, Vulkan functions use the armeabi-v7a-hard calling // convention, even if the application's native code is compiled with the // armeabi-v7a calling convention. diff --git a/core/cc/windows/get_gles_proc_address.cpp b/core/cc/windows/get_gles_proc_address.cpp deleted file mode 100644 index 2c3d344f03..0000000000 --- a/core/cc/windows/get_gles_proc_address.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "../get_gles_proc_address.h" -#include "../dl_loader.h" -#include "../log.h" -#include "../target.h" // STDCALL - -#include -#include - -#include -#include - -namespace { - -std::string systemOpengl32Path() { - char sysdir[MAX_PATH]; - GetSystemDirectoryA(sysdir, MAX_PATH - 1); - - std::stringstream path; - path << sysdir << "\\opengl32.dll"; - return path.str(); -} - -void* getGlesProcAddress(const char* name) { - using namespace core; - typedef void* (*GPAPROC)(const char* name); - - static DlLoader opengl(systemOpengl32Path().c_str()); - if (GPAPROC gpa = - reinterpret_cast(opengl.lookup("wglGetProcAddress"))) { - if (void* proc = gpa(name)) { - GAPID_DEBUG( - "GetGlesProcAddress(%s) -> 0x%x (via opengl32 wglGetProcAddress)", - name, proc); - return proc; - } - } - if (void* proc = opengl.lookup(name)) { - GAPID_DEBUG("GetGlesProcAddress(%s) -> 0x%x (from opengl32 symbol)", name, - proc); - return proc; - } - - GAPID_DEBUG("GetGlesProcAddress(%s) -> not found", name); - return nullptr; -} - -} // anonymous namespace - -namespace core { - -GetGlesProcAddressFunc* GetGlesProcAddress = getGlesProcAddress; -bool hasGLorGLES() { return DlLoader::can_load(systemOpengl32Path().c_str()); } - -} // namespace core diff --git a/core/codegen/BUILD.bazel b/core/codegen/BUILD.bazel deleted file mode 100644 index ae14dfba96..0000000000 --- a/core/codegen/BUILD.bazel +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "arithmetic.go", - "assert.go", - "builder.go", - "cast.go", - "datalayout.go", - "debug.go", - "doc.go", - "exceptions.go", - "function.go", - "intrinsics.go", - "module.go", - "output.go", - "triple.go", - "types.go", - "value.go", - ], - importpath = "github.com/google/gapid/core/codegen", - visibility = ["//visibility:public"], - deps = [ - "//core/app/linker:go_default_library", - "//core/math/sint:go_default_library", - "//core/os/device:go_default_library", - "@llvm//:go", - ], -) - -go_test( - name = "go_default_test", - srcs = [ - "codegen_test.go", - "triple_test.go", - ], - embed = [":go_default_library"], - deps = [ - "//core/assert:go_default_library", - "//core/codegen/call:go_default_library", - "//core/log:go_default_library", - "//core/os/device:go_default_library", - "//core/os/device/host:go_default_library", - ], -) diff --git a/core/codegen/arithmetic.go b/core/codegen/arithmetic.go deleted file mode 100644 index cfe791e9d9..0000000000 --- a/core/codegen/arithmetic.go +++ /dev/null @@ -1,363 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "fmt" - "reflect" - - "llvm/bindings/go/llvm" -) - -// Zero returns a zero-value of the specified type. -func (b *Builder) Zero(ty Type) *Value { - if ty == b.m.Types.Void { - return nil - } - return b.val(ty, llvm.ConstNull(ty.llvmTy())).SetName("zero") -} - -// One returns a one-value of the specified bool, int or float type. -func (b *Builder) One(ty Type) *Value { - var value *Value - switch { - case ty == b.m.Types.Bool: - value = b.val(ty, llvm.ConstInt(ty.llvmTy(), 1, false)) - case IsInteger(ty): - value = b.val(ty, llvm.ConstInt(ty.llvmTy(), 1, false)) - case IsFloat(ty): - value = b.val(ty, llvm.ConstFloat(ty.llvmTy(), 1)) - default: - fail("One does not support type %T", ty) - } - value.SetName("1") - return value -} - -// Not returns !x. The type of x must be Bool. -func (b *Builder) Not(x *Value) *Value { - assertTypesEqual(x.ty, b.m.Types.Bool) - return b.val(b.m.Types.Bool, b.llvm.CreateNot(x.llvm, "!"+x.Name())) -} - -// Invert returns ~x. -func (b *Builder) Invert(x *Value) *Value { - return b.val(x.ty, b.llvm.CreateNot(x.llvm, "")) -} - -// Negate returns -x. The type of x must be a signed integer or float. -func (b *Builder) Negate(x *Value) *Value { - ty := Scalar(x.Type()) - name := "-" + x.Name() - switch { - case IsSignedInteger(ty): - return b.val(ty, b.llvm.CreateNeg(x.llvm, name)) - case IsFloat(ty): - return b.val(ty, b.llvm.CreateFNeg(x.llvm, name)) - default: - panic(fmt.Errorf("Cannot divide values of type %v", ty)) - } -} - -func (b *Builder) cmp(x *Value, op string, y *Value, ucmp, scmp llvm.IntPredicate, fcmp llvm.FloatPredicate) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := x.Type() - if vec, isVec := ty.(Vector); isVec { - ty = vec.Element - } - var v *Value - switch { - case IsSignedInteger(ty): - v = b.val(b.m.Types.Bool, b.llvm.CreateICmp(scmp, x.llvm, y.llvm, "")) - case IsUnsignedInteger(ty), IsPointer(ty), IsBool(ty): - v = b.val(b.m.Types.Bool, b.llvm.CreateICmp(ucmp, x.llvm, y.llvm, "")) - case IsFloat(ty): - v = b.val(b.m.Types.Bool, b.llvm.CreateFCmp(fcmp, x.llvm, y.llvm, "")) - default: - panic(fmt.Errorf("Cannot compare %v types with %v", ty.TypeName(), op)) - } - v.SetName(x.Name() + op + y.Name()) - return v -} - -// Equal returns x == y. The types of the two values must be equal. -func (b *Builder) Equal(x, y *Value) *Value { - ty := x.Type() - if ty, ok := ty.(*Struct); ok { - assertTypesEqual(x.Type(), y.Type()) - var eq *Value - for i, f := range ty.Fields() { - x, y := x.Extract(f.Name), y.Extract(f.Name) - if i == 0 { - eq = b.Equal(x, y) - } else { - eq = b.And(eq, b.Equal(x, y)) - } - } - return eq - } - return b.cmp(x, "==", y, llvm.IntEQ, llvm.IntEQ, llvm.FloatOEQ) -} - -// NotEqual returns x != y. The types of the two values must be equal. -func (b *Builder) NotEqual(x, y *Value) *Value { - ty := x.Type() - if ty, ok := ty.(*Struct); ok { - assertTypesEqual(x.Type(), y.Type()) - var neq *Value - for i, f := range ty.Fields() { - x, y := x.Extract(f.Name), y.Extract(f.Name) - if i == 0 { - neq = b.NotEqual(x, y) - } else { - neq = b.Or(neq, b.NotEqual(x, y)) - } - } - return neq - } - return b.cmp(x, "!=", y, llvm.IntNE, llvm.IntNE, llvm.FloatONE) -} - -// GreaterThan returns x > y. The types of the two values must be equal. -func (b *Builder) GreaterThan(x, y *Value) *Value { - return b.cmp(x, ">", y, llvm.IntUGT, llvm.IntSGT, llvm.FloatOGT) -} - -// LessThan returns x < y. The types of the two values must be equal. -func (b *Builder) LessThan(x, y *Value) *Value { - return b.cmp(x, "<", y, llvm.IntULT, llvm.IntSLT, llvm.FloatOLT) -} - -// GreaterOrEqualTo returns x >= y. The types of the two values must be equal. -func (b *Builder) GreaterOrEqualTo(x, y *Value) *Value { - return b.cmp(x, ">=", y, llvm.IntUGE, llvm.IntSGE, llvm.FloatOGE) -} - -// LessOrEqualTo returns x <= y. The types of the two values must be equal. -func (b *Builder) LessOrEqualTo(x, y *Value) *Value { - return b.cmp(x, "<=", y, llvm.IntULE, llvm.IntSLE, llvm.FloatOLE) -} - -// Add returns x + y. The types of the two values must be equal. -func (b *Builder) Add(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := Scalar(x.Type()) - switch { - case IsInteger(ty): - return b.val(x.Type(), b.llvm.CreateAdd(x.llvm, y.llvm, x.Name()+"+"+y.Name())) - case IsFloat(ty): - return b.val(x.Type(), b.llvm.CreateFAdd(x.llvm, y.llvm, x.Name()+"+"+y.Name())) - default: - panic(fmt.Errorf("Cannot add values of type %v", ty)) - } -} - -// AddS returns x + y. The types of the two values must be equal. -func (b *Builder) AddS(x *Value, y interface{}) *Value { - return b.Add(x, b.Scalar(y)) -} - -// Sub returns x - y. The types of the two values must be equal. -func (b *Builder) Sub(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := Scalar(x.Type()) - switch { - case IsInteger(ty): - return b.val(x.Type(), b.llvm.CreateSub(x.llvm, y.llvm, x.Name()+"-"+y.Name())) - case IsFloat(ty): - return b.val(x.Type(), b.llvm.CreateFSub(x.llvm, y.llvm, x.Name()+"-"+y.Name())) - default: - panic(fmt.Errorf("Cannot subtract values of type %v", ty)) - } -} - -// SubS returns x - y. The types of the two values must be equal. -func (b *Builder) SubS(x *Value, y interface{}) *Value { - return b.Sub(x, b.Scalar(y)) -} - -// Mul returns x * y. The types of the two values must be equal. -func (b *Builder) Mul(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := Scalar(x.Type()) - switch { - case IsInteger(ty): - return b.val(x.Type(), b.llvm.CreateMul(x.llvm, y.llvm, x.Name()+"*"+y.Name())) - case IsFloat(ty): - return b.val(x.Type(), b.llvm.CreateFMul(x.llvm, y.llvm, x.Name()+"*"+y.Name())) - default: - panic(fmt.Errorf("Cannot multiply values of type %v", ty)) - } -} - -// MulS returns x * y. The types of the two values must be equal. -func (b *Builder) MulS(x *Value, y interface{}) *Value { - return b.Mul(x, b.Scalar(y)) -} - -// Div returns x / y. The types of the two values must be equal. -func (b *Builder) Div(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := Scalar(x.Type()) - name := x.Name() + "/" + y.Name() - switch { - case IsSignedInteger(ty): - return b.val(ty, b.llvm.CreateSDiv(x.llvm, y.llvm, name)) - case IsUnsignedInteger(ty): - return b.val(ty, b.llvm.CreateUDiv(x.llvm, y.llvm, name)) - case IsFloat(ty): - return b.val(ty, b.llvm.CreateFDiv(x.llvm, y.llvm, name)) - default: - panic(fmt.Errorf("Cannot divide values of type %v", ty)) - } -} - -// Rem returns x % y. The types of the two values must be equal. -func (b *Builder) Rem(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := Scalar(x.Type()) - name := x.Name() + "%" + y.Name() - switch { - case IsSignedInteger(ty): - return b.val(ty, b.llvm.CreateSRem(x.llvm, y.llvm, name)) - case IsUnsignedInteger(ty): - return b.val(ty, b.llvm.CreateURem(x.llvm, y.llvm, name)) - case IsFloat(ty): - return b.val(ty, b.llvm.CreateFRem(x.llvm, y.llvm, name)) - default: - panic(fmt.Errorf("Cannot divide values of type %v", ty)) - } -} - -// DivS returns x / y. The types of the two values must be equal. -func (b *Builder) DivS(x *Value, y interface{}) *Value { - return b.Div(x, b.Scalar(y)) -} - -// And performs a bitwise-and of the two integers. -// The types of the two values must be equal. -func (b *Builder) And(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - return b.val(x.Type(), b.llvm.CreateAnd(x.llvm, y.llvm, x.Name()+"&"+y.Name())) -} - -// Or performs a bitwise-or of the two integers. -// The types of the two values must be equal. -func (b *Builder) Or(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - return b.val(x.Type(), b.llvm.CreateOr(x.llvm, y.llvm, x.Name()+"|"+y.Name())) -} - -// Xor performs a bitwise-xor of the two integers. -// The types of the two values must be equal. -func (b *Builder) Xor(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - return b.val(x.Type(), b.llvm.CreateXor(x.llvm, y.llvm, x.Name()+"^"+y.Name())) -} - -// Shuffle performs a vector-shuffle operation of the two vector values x and y -// with the specified indices. -func (b *Builder) Shuffle(x, y, indices *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - assertVectorsSameLength(x.Type(), y.Type()) - dataTy, ok := x.Type().(Vector) - if !ok { - panic(fmt.Errorf("Shuffle must be passed vector types, got: %v", x.Type())) - } - indicesTy, ok := indices.Type().(Vector) - if !ok || indicesTy.Element != b.m.Types.Int32 { - panic(fmt.Errorf("Shuffle indices must be a vector of Int32, got: %v", indices.Type())) - } - ty := b.m.Types.Vector(dataTy.Element, indicesTy.Count) - return b.val(ty, b.llvm.CreateShuffleVector(x.llvm, y.llvm, indices.llvm, "shuffle")) -} - -// ShiftLeft performs a bit-shift left by shift bits. -func (b *Builder) ShiftLeft(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - return b.val(x.Type(), b.llvm.CreateShl(x.llvm, y.llvm, x.Name()+"<<"+y.Name())) -} - -// ShiftRight performs a bit-shift right by shift bits. -func (b *Builder) ShiftRight(x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - ty := x.Type() - switch { - case IsSignedInteger(ty): - return b.val(ty, b.llvm.CreateAShr(x.llvm, y.llvm, x.Name()+">>"+y.Name())) - case IsUnsignedInteger(ty): - return b.val(ty, b.llvm.CreateLShr(x.llvm, y.llvm, x.Name()+">>"+y.Name())) - default: - panic(fmt.Errorf("Cannot divide values of type %v", ty)) - } -} - -// Select returns (cond ? x : y). x and y must be of the same type. -func (b *Builder) Select(cond, x, y *Value) *Value { - assertTypesEqual(x.Type(), y.Type()) - assertTypesEqual(cond.Type(), b.m.Types.Bool) - return b.val(x.Type(), b.llvm.CreateSelect(cond.llvm, x.llvm, y.llvm, - fmt.Sprintf("(%v?%v:%v)", cond.Name(), x.Name(), y.Name()))) -} - -// Scalar returns a constant scalar with the value v. -func (b *Builder) Scalar(v interface{}) *Value { - ty := b.m.Types.TypeOf(v) - switch { - case IsStruct(ty): - ty := ty.(*Struct) - val := llvm.Undef(ty.llvm) - r := reflect.ValueOf(v) - for i, c := 0, r.NumField(); i < c; i++ { - f := b.Scalar(r.Field(i).Interface()).llvm - val = b.llvm.CreateInsertValue(val, f, i, "") - } - return b.val(ty, val) - - default: - return b.m.Scalar(v).Value(b) - } -} - -// Vector returns a constant vector with the specified values. -func (b *Builder) Vector(el0 interface{}, els ...interface{}) *Value { - types := make([]Type, len(els)+1) - values := make([]llvm.Value, len(els)+1) - v := b.Scalar(el0) - types[0] = v.Type() - values[0] = v.llvm - allSameType := true - for i, el := range els { - v := b.Scalar(el) - types[i+1] = v.Type() - values[i+1] = v.llvm - allSameType = types[0] == types[i+1] - } - if !allSameType { - fail("Vector passed mix of element types: %v", types) - } - return b.val(b.m.Types.Vector(types[0], len(values)), llvm.ConstVector(values, false)) -} - -// VectorN returns a constant vector of n elements with the same value v in each -// element. -func (b *Builder) VectorN(n int, v interface{}) *Value { - values := make([]llvm.Value, n) - s := b.Scalar(v) - for i := range values { - values[i] = s.llvm - } - return b.val(b.m.Types.Vector(s.Type(), len(values)), llvm.ConstVector(values, false)) -} diff --git a/core/codegen/assert.go b/core/codegen/assert.go deleted file mode 100644 index 80266cd7ce..0000000000 --- a/core/codegen/assert.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import "fmt" - -func assertTypesEqual(x, y Type) { - if x != y { - panic(fmt.Errorf("Arguments have differing types: %v and %v", x.TypeName(), y.TypeName())) - } -} - -func assertVectorsSameLength(x, y Type) { - vecX, ok := x.(Vector) - if !ok { - return - } - vecY, ok := y.(Vector) - if !ok { - return - } - if vecX.Count != vecY.Count { - panic(fmt.Errorf("Expected vectors of same length, got %v and %v", x.TypeName(), y.TypeName())) - } -} diff --git a/core/codegen/builder.go b/core/codegen/builder.go deleted file mode 100644 index 2d464a38e2..0000000000 --- a/core/codegen/builder.go +++ /dev/null @@ -1,412 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "fmt" - "strings" - - "llvm/bindings/go/llvm" -) - -// IndexOrName can be an index (int) or field name (string). -type IndexOrName interface{} - -// ValueIndexOrName can be an index (Value or int) or field name (string). -type ValueIndexOrName interface{} - -// Builder is provided to the callback of Build() for building a function's body. -type Builder struct { - function *Function // The function being built. - params []*Value // Function parameter values. - entry llvm.BasicBlock // Function entry block. - exit llvm.BasicBlock // Function exit block. - result llvm.Value // Function return value. - llvm llvm.Builder - m *Module -} - -// buildFailure is a special type thrown as a panic by fail(). -// They are caught in Build(). -type buildFailure string - -func (f buildFailure) String() string { return string(f) } - -func fail(msg string, args ...interface{}) { - str := fmt.Sprintf(msg, args...) - panic(buildFailure(str)) -} - -// Call invokes the function f with the specified arguments -func (b *Builder) Call(f *Function, args ...*Value) *Value { - return b.call(f.llvm, f.Type.Signature, f.Name, args) -} - -// CallIndirect invokes the function pointer f with the specified arguments -func (b *Builder) CallIndirect(f *Value, args ...*Value) *Value { - var fty *FunctionType - if ptrTy, ok := Underlying(f.Type()).(Pointer); ok { - fty, _ = Underlying(ptrTy.Element).(*FunctionType) - } - if fty == nil { - fail("CallIndirect() can only be called with function pointers. Got %v", f.Type()) - } - return b.call(f.llvm, fty.Signature, f.name, args) -} - -func (b *Builder) call(fn llvm.Value, sig Signature, name string, args []*Value) *Value { - l := b.callArgs(sig, name, args) - if sig.Result == b.m.Types.Void { - name = "" - } - return b.val(sig.Result, b.llvm.CreateCall(fn, l, name)) -} - -// Invoke invokes the function f with the specified arguments. -// If an exception is thrown while calling f, then cleanup will be called before -// rethrowing the exception. -func (b *Builder) Invoke(f *Function, cleanup func(), args ...*Value) *Value { - fn, sig, name := f.llvm, f.Type.Signature, f.Name - - l := b.callArgs(sig, name, args) - - then := b.m.ctx.AddBasicBlock(b.function.llvm, fmt.Sprintf("%v_nothrow", name)) - throw := b.m.ctx.AddBasicBlock(b.function.llvm, fmt.Sprintf("%v_catch", name)) - - if sig.Result == b.m.Types.Void { - name = "" - } - - res := b.val(sig.Result, b.llvm.CreateInvoke(fn, l, then, throw, name)) - - b.function.llvm.SetPersonality(b.m.exceptions.personalityFn.llvm) - - b.block(throw, llvm.BasicBlock{}, func() { - lp := b.llvm.CreateLandingPad(b.m.exceptions.exceptionTy.llvmTy(), 0, "cleanup") - lp.SetCleanup(true) - cleanup() - b.llvm.CreateResume(lp) - }) - - b.setInsertPointAtEnd(then) - - return res -} - -// Throw throws an exception with the given value. -func (b *Builder) Throw(v *Value) { - b.m.exceptions.throw(b, v) -} - -func (b *Builder) callArgs(sig Signature, name string, args []*Value) []llvm.Value { - if sig.Variadic { - if g, e := len(args), len(sig.Parameters); g < e { - fail("Got %d arguments, but needed %d to call %v", g, e, sig.string(name)) - } - } else if g, e := len(args), len(sig.Parameters); g != e { - fail("Got %d arguments, but needed %d to call %v", g, e, sig.string(name)) - } - l := make([]llvm.Value, len(args)) - for i, a := range args { - if a == nil { - fail("Argument %d is nil when attempting to call %v", i, sig.string(name)) - } - l[i] = a.llvm - if i < len(sig.Parameters) { - if g, e := a.ty, sig.Parameters[i]; g != e { - fail("Incorrect argument type for parameter %d when calling %v: Got %v, expected %v", - i, sig.string(name), g.TypeName(), e.TypeName()) - } - } - } - return l -} - -// Parameter returns i'th function parameter -func (b *Builder) Parameter(i int) *Value { - return b.params[i] -} - -// Undef returns a new undefined value of the specified type. -func (b *Builder) Undef(ty Type) *Value { - return b.val(ty, llvm.Undef(ty.llvmTy())) -} - -// Local returns a pointer to a new local variable with the specified name and -// type. -func (b *Builder) Local(name string, ty Type) *Value { - block := b.llvm.GetInsertBlock() - b.llvm.SetInsertPoint(b.entry, b.entry.FirstInstruction()) - local := b.llvm.CreateAlloca(ty.llvmTy(), "") - b.setInsertPointAtEnd(block) - return b.val(b.m.Types.Pointer(ty), local).SetName(name) -} - -// LocalInit returns a new local variable with the specified name and initial value. -func (b *Builder) LocalInit(name string, val *Value) *Value { - local := b.Local(name, val.ty) - local.Store(val) - return local -} - -// If builds an if statement. -func (b *Builder) If(cond *Value, onTrue func()) { - b.IfElse(cond, onTrue, nil) -} - -// IfElse builds an if-else statement. -func (b *Builder) IfElse(cond *Value, onTrue, onFalse func()) { - trueBlock := b.m.ctx.AddBasicBlock(b.function.llvm, "if_true") - var falseBlock llvm.BasicBlock - if onFalse != nil { - falseBlock = b.m.ctx.AddBasicBlock(b.function.llvm, "if_false") - } - endBlock := b.m.ctx.AddBasicBlock(b.function.llvm, "end_if") - if onFalse == nil { - falseBlock = endBlock - } - - b.llvm.CreateCondBr(cond.llvm, trueBlock, falseBlock) - - b.block(trueBlock, endBlock, onTrue) - - if onFalse != nil { - b.block(falseBlock, endBlock, onFalse) - } - - b.setInsertPointAtEnd(endBlock) -} - -// While builds a logic block with the following psuedocode: -// -// while test() { -// loop() -// } -// -func (b *Builder) While(test func() *Value, loop func()) { - testBlock := b.m.ctx.AddBasicBlock(b.function.llvm, "while_test") - loopBlock := b.m.ctx.AddBasicBlock(b.function.llvm, "while_loop") - exitBlock := b.m.ctx.AddBasicBlock(b.function.llvm, "while_exit") - - b.llvm.CreateBr(testBlock) - - b.block(testBlock, llvm.BasicBlock{}, func() { - cond := test() - if !b.IsBlockTerminated() { - b.llvm.CreateCondBr(cond.llvm, loopBlock, exitBlock) - } - }) - - b.block(loopBlock, testBlock, loop) - - b.setInsertPointAtEnd(exitBlock) -} - -// ForN builds a logic block with the following psuedocode: -// -// for it := 0; it < n; it++ { -// cont := cb() -// if cont == false { break; } -// } -// -// If cb returns nil then the loop will never exit early. -func (b *Builder) ForN(n *Value, cb func(iterator *Value) (cont *Value)) { - one := llvm.ConstInt(n.Type().llvmTy(), 1, false) - zero := b.Zero(n.Type()) - iterator := b.LocalInit("loop_iterator", zero) - - test := b.m.ctx.AddBasicBlock(b.function.llvm, "for_n_test") - loop := b.m.ctx.AddBasicBlock(b.function.llvm, "for_n_loop") - exit := b.m.ctx.AddBasicBlock(b.function.llvm, "for_n_exit") - - b.llvm.CreateBr(test) - - b.block(test, llvm.BasicBlock{}, func() { - done := b.llvm.CreateICmp(llvm.IntSLT, iterator.Load().llvm, n.llvm, "for_n_condition") - b.llvm.CreateCondBr(done, loop, exit) - }) - - b.block(loop, llvm.BasicBlock{}, func() { - it := iterator.Load() - cont := cb(it) - if b.IsBlockTerminated() { - return - } - b.llvm.CreateStore(b.llvm.CreateAdd(it.llvm, one, "for_n_iterator_inc"), iterator.llvm) - if cont == nil { - b.llvm.CreateBr(test) - } else { - assertTypesEqual(cont.ty, b.m.Types.Bool) - b.llvm.CreateCondBr(cont.llvm, test, exit) - } - }) - - b.setInsertPointAtEnd(exit) -} - -// SwitchCase is a single condition and block used as a case statement in a -// switch. -type SwitchCase struct { - Conditions func() []*Value - Block func() -} - -// Switch builds a switch statement. -func (b *Builder) Switch(cases []SwitchCase, defaultCase func()) { - tests := make([]llvm.BasicBlock, len(cases)) - blocks := make([]llvm.BasicBlock, len(cases)) - for i := range cases { - tests[i] = b.m.ctx.AddBasicBlock(b.function.llvm, fmt.Sprintf("switch_case_%d_test", i)) - blocks[i] = b.m.ctx.AddBasicBlock(b.function.llvm, fmt.Sprintf("switch_case_%d_block", i)) - } - - var defaultBlock llvm.BasicBlock - if defaultCase != nil { - defaultBlock = b.m.ctx.AddBasicBlock(b.function.llvm, "switch_case_default") - tests = append(tests, defaultBlock) - } - - exit := b.m.ctx.AddBasicBlock(b.function.llvm, "end_switch") - - b.llvm.CreateBr(tests[0]) - - for i, c := range cases { - i, c := i, c - b.block(tests[i], llvm.BasicBlock{}, func() { - conds := c.Conditions() - match := conds[0] - for _, c := range conds[1:] { - match = b.Or(match, c) - } - next := exit - if i+1 < len(tests) { - next = tests[i+1] - } - b.llvm.CreateCondBr(match.llvm, blocks[i], next) - }) - b.block(blocks[i], exit, c.Block) - } - - if defaultCase != nil { - b.block(defaultBlock, exit, defaultCase) - } - - b.setInsertPointAtEnd(exit) -} - -// Return returns execution of the function with the given value -func (b *Builder) Return(val *Value) { - if val != nil { - assertTypesEqual(val.Type(), b.function.Type.Signature.Result) - b.llvm.CreateStore(val.llvm, b.result) - } else if !b.result.IsNil() { - b.llvm.CreateStore(llvm.ConstNull(b.function.Type.Signature.Result.llvmTy()), b.result) - } - b.llvm.CreateBr(b.exit) -} - -// IsBlockTerminated returns true if the last instruction is a terminator -// (unconditional jump). It is illegal to write another instruction after a -// terminator. -func (b *Builder) IsBlockTerminated() bool { - return !b.llvm.GetInsertBlock().LastInstruction().IsATerminatorInst().IsNil() -} - -// FuncAddr returns the pointer to the given function. -func (b *Builder) FuncAddr(f *Function) *Value { - return b.val(b.m.Types.Pointer(f.Type), f.llvm) -} - -// PrintfSpecifier returns the string and values that can be used to print v -// with printf. -func (b *Builder) PrintfSpecifier(v *Value) (string, []*Value) { - t := v.Type() - switch t { - case b.m.Types.Bool: - v = b.Select(v, b.Scalar("true"), b.Scalar("false")) - return "%s", []*Value{v} - case b.m.Types.Float32: - return "%f", []*Value{v} - case b.m.Types.Float64: - return "%d", []*Value{v} - case b.m.Types.Int, b.m.Types.Int8, b.m.Types.Int16, b.m.Types.Int32, b.m.Types.Int64: - return "%lld", []*Value{v.Cast(b.m.Types.Int64)} - case b.m.Types.Uint, b.m.Types.Uint8, b.m.Types.Uint16, b.m.Types.Uint32, b.m.Types.Uint64: - return "%llu", []*Value{v.Cast(b.m.Types.Int64)} - case b.m.Types.Uintptr: - return "%p", []*Value{v.Cast(b.m.Types.Int64)} - case b.m.Types.Size: - return "%z", []*Value{v.Cast(b.m.Types.Int64)} - case b.m.Types.Float32: - return "%f", []*Value{v} - case b.m.Types.Float64: - return "%d", []*Value{v} - } - switch t := t.(type) { - case Pointer: - return "%p", []*Value{v} - case *Struct: - vals := []*Value{} - sb := strings.Builder{} - sb.WriteString(t.TypeName()) - sb.WriteString("{ ") - for i, f := range t.Fields() { - if i > 0 { - sb.WriteString(", ") - } - fmt, val := b.PrintfSpecifier(v.Extract(i)) - sb.WriteString(f.Name) - sb.WriteString(": ") - sb.WriteString(fmt) - vals = append(vals, val...) - } - sb.WriteString(" }") - return sb.String(), vals - } - return fmt.Sprintf("<%v>", v.Type()), nil -} - -// StructOf builds a struct value that holds all the values in v. -func (b *Builder) StructOf(name string, v []*Value) *Value { - fields := make([]Field, len(v)) - for i, v := range v { - fields[i].Type = v.Type() - } - s := b.Undef(b.m.Types.Struct(name, fields...)) - for i, v := range v { - s = s.Insert(i, v) - } - return s -} - -// block calls f to appends instructions to the specified block. -// If next is not nil and the f returns without terminating the block, then a -// unconditional jump to next is added to the block. -func (b *Builder) block(block, next llvm.BasicBlock, f func()) { - b.setInsertPointAtEnd(block) - - f() - - if !next.IsNil() && !b.IsBlockTerminated() { - b.llvm.CreateBr(next) - } -} - -func (b *Builder) setInsertPointAtEnd(block llvm.BasicBlock) { - b.llvm.SetInsertPointAtEnd(block) - // LLVM will clear the debug location on a insert point change. - // Restore it to what we previously had. - b.dbgRestoreLocation() -} diff --git a/core/codegen/call/BUILD.bazel b/core/codegen/call/BUILD.bazel deleted file mode 100644 index 27c3a7546d..0000000000 --- a/core/codegen/call/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["call.go"], - cgo = True, - clinkopts = [], # keep - importpath = "github.com/google/gapid/core/codegen/call", - visibility = ["//visibility:public"], -) diff --git a/core/codegen/call/call.go b/core/codegen/call/call.go deleted file mode 100644 index 60f8e0b7cf..0000000000 --- a/core/codegen/call/call.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// Package call provides methods for invoking C function pointers. -package call - -import "unsafe" - -// void V(void* f) { ((void (*)())(f))(); } -// int I(void* f) { return ((int (*)())(f))(); } -// int III(void* f, int a, int b) { return ((int (*)(int, int))(f))(a, b); } -import "C" - -// V invokes the function f that has the signature void(). -func V(f unsafe.Pointer) { C.V(f) } - -// I invokes the function f that has the signature int(). -func I(f unsafe.Pointer) int { return (int)(C.I(f)) } - -// III invokes the function f that has the signature int(int, int). -func III(f unsafe.Pointer, a, b int) int { return (int)(C.III(f, (C.int)(a), (C.int)(b))) } diff --git a/core/codegen/cast.go b/core/codegen/cast.go deleted file mode 100644 index 64f937134c..0000000000 --- a/core/codegen/cast.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "llvm/bindings/go/llvm" -) - -// Cast casts the value v to the type ty. -func (v *Value) Cast(ty Type) *Value { - srcTy, dstTy := v.Type(), ty - if srcTy == dstTy { - return v // No-op - } - if IsVector(srcTy) != IsVector(dstTy) { - fail("Cannot cast between vector and non-vector types. (%v -> %v)", srcTy, dstTy) - } - if IsPointer(srcTy) && IsPointer(dstTy) { - name := srcTy.TypeName() + "->" + dstTy.TypeName() - return v.b.val(ty, v.b.llvm.CreatePointerCast(v.llvm, ty.llvmTy(), name)) - } - assertVectorsSameLength(srcTy, dstTy) - srcElTy, dstElTy := Scalar(srcTy), Scalar(dstTy) - grow := srcElTy.SizeInBits() < dstElTy.SizeInBits() - shink := srcElTy.SizeInBits() > dstElTy.SizeInBits() - switch { - case IsInteger(srcElTy) && IsPointer(dstElTy): - return v.b.val(ty, v.b.llvm.CreateIntToPtr(v.llvm, ty.llvmTy(), "")) - case IsPointer(srcElTy) && IsInteger(dstElTy): - return v.b.val(ty, v.b.llvm.CreatePtrToInt(v.llvm, ty.llvmTy(), "")) - case IsSignedInteger(srcElTy) && IsFloat(dstElTy): - return v.b.val(ty, v.b.llvm.CreateSIToFP(v.llvm, ty.llvmTy(), "")) - case IsUnsignedInteger(srcElTy) && IsFloat(dstElTy): - return v.b.val(ty, v.b.llvm.CreateUIToFP(v.llvm, ty.llvmTy(), "")) - case IsFloat(srcElTy) && IsSignedInteger(dstElTy): - return v.b.val(ty, v.b.llvm.CreateFPToSI(v.llvm, ty.llvmTy(), "")) - case IsFloat(srcElTy) && IsUnsignedInteger(dstElTy): - return v.b.val(ty, v.b.llvm.CreateFPToUI(v.llvm, ty.llvmTy(), "")) - case IsBool(srcElTy) && IsInteger(dstElTy): - return v.b.val(ty, v.b.llvm.CreateZExt(v.llvm, ty.llvmTy(), "")) - case IsInteger(srcElTy) && IsBool(dstElTy): - return v.b.NotEqual(v, v.b.Zero(srcElTy)) - case IsUnsignedInteger(srcElTy) && IsIntegerOrEnum(dstElTy) && grow: - return v.b.val(ty, v.b.llvm.CreateZExt(v.llvm, ty.llvmTy(), "")) - case IsSignedIntegerOrEnum(srcElTy) && IsIntegerOrEnum(dstElTy) && grow: - return v.b.val(ty, v.b.llvm.CreateSExt(v.llvm, ty.llvmTy(), "")) - case IsIntegerOrEnum(srcElTy) && IsIntegerOrEnum(dstElTy) && shink: - return v.b.val(ty, v.b.llvm.CreateTrunc(v.llvm, ty.llvmTy(), "")) - case IsIntegerOrEnum(srcElTy) && IsIntegerOrEnum(dstElTy): - return v.b.val(ty, v.llvm) // signed conversion - case IsFloat(srcElTy) && IsFloat(dstElTy) && shink: - return v.b.val(ty, v.b.llvm.CreateFPTrunc(v.llvm, ty.llvmTy(), "")) - case IsFloat(srcElTy) && IsFloat(dstElTy) && grow: - return v.b.val(ty, v.b.llvm.CreateFPExt(v.llvm, ty.llvmTy(), "")) - default: - fail("Cannot cast from %v -> %v", srcTy.TypeName(), dstTy.TypeName()) - return nil - } -} - -// Bitcast performs a bitwise cast of the value v to the type ty. -func (v *Value) Bitcast(ty Type) *Value { - srcTy, dstTy := v.Type(), ty - if srcTy == dstTy { - return v // No-op - } - if srcSize, dstSize := srcTy.SizeInBits(), dstTy.SizeInBits(); srcSize != dstSize { - fail("Bitcast cannot change sizes. (%v -> %v)", srcTy, dstTy) - } - return v.b.val(ty, v.b.llvm.CreateBitCast(v.llvm, ty.llvmTy(), "bitcast")) -} - -// Cast casts the constant v to the type ty. -func (v Const) Cast(ty Type) Const { - srcTy, dstTy := v.Type, ty - if srcTy == dstTy { - return v // No-op - } - if IsVector(srcTy) != IsVector(dstTy) { - fail("Cannot cast between vector and non-vector types. (%v -> %v)", srcTy, dstTy) - } - if IsPointer(srcTy) && IsPointer(dstTy) { - return Const{ty, llvm.ConstPointerCast(v.llvm, ty.llvmTy())} - } - assertVectorsSameLength(srcTy, dstTy) - srcElTy, dstElTy := Scalar(srcTy), Scalar(dstTy) - grow := srcElTy.SizeInBits() < dstElTy.SizeInBits() - shink := srcElTy.SizeInBits() > dstElTy.SizeInBits() - switch { - case IsInteger(srcElTy) && IsPointer(dstElTy): - return Const{ty, llvm.ConstIntToPtr(v.llvm, ty.llvmTy())} - case IsSignedInteger(srcElTy) && IsFloat(dstElTy): - return Const{ty, llvm.ConstSIToFP(v.llvm, ty.llvmTy())} - case IsUnsignedInteger(srcElTy) && IsFloat(dstElTy): - return Const{ty, llvm.ConstUIToFP(v.llvm, ty.llvmTy())} - case IsFloat(srcElTy) && IsSignedInteger(dstElTy): - return Const{ty, llvm.ConstFPToSI(v.llvm, ty.llvmTy())} - case IsFloat(srcElTy) && IsUnsignedInteger(dstElTy): - return Const{ty, llvm.ConstFPToUI(v.llvm, ty.llvmTy())} - case IsBool(srcElTy) && IsInteger(dstElTy): - return Const{ty, llvm.ConstZExt(v.llvm, ty.llvmTy())} - case IsInteger(srcElTy) && IsUnsignedInteger(dstElTy) && grow: - return Const{ty, llvm.ConstZExt(v.llvm, ty.llvmTy())} - case IsInteger(srcElTy) && IsSignedInteger(dstElTy) && grow: - return Const{ty, llvm.ConstSExt(v.llvm, ty.llvmTy())} - case IsInteger(srcElTy) && IsInteger(dstElTy) && shink: - return Const{ty, llvm.ConstTrunc(v.llvm, ty.llvmTy())} - case IsInteger(srcElTy) && IsInteger(dstElTy): - return Const{ty, v.llvm} // signed conversion - default: - fail("Cannot cast from %v -> %v", srcTy.TypeName(), dstTy.TypeName()) - return Const{} - } -} - -// Cast casts the global value to the given type. -func (v Global) Cast(ty Type) Global { - return Global(Const(v).Cast(ty)) -} diff --git a/core/codegen/codegen_test.go b/core/codegen/codegen_test.go deleted file mode 100644 index f10b9dd71a..0000000000 --- a/core/codegen/codegen_test.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen_test - -import ( - "testing" - - "github.com/google/gapid/core/assert" - "github.com/google/gapid/core/codegen" - "github.com/google/gapid/core/codegen/call" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/os/device/host" -) - -func TestCodegen(t *testing.T) { - ctx := log.Testing(t) - - hostABI := host.Instance(ctx).Configuration.ABIs[0] - - m := codegen.NewModule("test", hostABI) - f := m.Function(m.Types.Int, "add", m.Types.Int, m.Types.Int) - err := f.Build(func(b *codegen.Builder) { - x := b.Parameter(0) - y := b.Parameter(1) - b.Return(b.Add(x, y)) - }) - if !assert.For(ctx, "f.Build").ThatError(err).Succeeded() { - return - } - e, err := m.Executor(false) - if !assert.For(ctx, "m.Executor(false)").ThatError(err).Succeeded() { - return - } - x := call.III(e.FunctionAddress(f), 2, 3) - assert.For(ctx, "call.III('add', 2, 3)").ThatInteger(x).Equals(5) -} diff --git a/core/codegen/datalayout.go b/core/codegen/datalayout.go deleted file mode 100644 index e13933a865..0000000000 --- a/core/codegen/datalayout.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package codegen - -import ( - "github.com/google/gapid/core/os/device" -) - -// DataLayout returns an LLVM data layout string for the given ABI, or an empty -// string if there is no known data layout for the given ABI. -// Reference: https://llvm.org/docs/LangRef.html#langref-datalayout -func DataLayout(abi *device.ABI) string { - switch abi.Architecture { - case device.ARMv7a: - // clang -target armv7-none-linux-androideabi -march=armv7-a - return "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" - case device.ARMv8a: - // clang -target aarch64-none-linux-androideabi -march=armv8 - return "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128" - case device.X86_64: - return "e-m:e-i64:64-f80:128-n8:16:32:64-S128" - } - return "" -} diff --git a/core/codegen/debug.go b/core/codegen/debug.go deleted file mode 100644 index 6a98e67727..0000000000 --- a/core/codegen/debug.go +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package codegen - -import ( - "debug/dwarf" - "fmt" - "llvm/bindings/go/llvm" - "path/filepath" -) - -// dbg holds debug info generation data. -type dbg struct { - m *Module - cu llvm.Metadata - llvm *llvm.DIBuilder - files map[string]file - tys map[Type]llvm.Metadata -} - -// builder constructs and returns a new llvm debug info builder for the module. -func (d *dbg) builder() *llvm.DIBuilder { - if d.llvm != nil { - return d.llvm - } - - b := llvm.NewDIBuilder(d.m.llvm) - d.cu = b.CreateCompileUnit(llvm.DICompileUnit{ - Language: 0x0001, // DW_LANG_C, - File: "apis", - Dir: "/ssd/src/gapid", // TODO: HACK - Producer: "gapid", - RuntimeVersion: 1, - }) - d.llvm = b - - // Build the basic types. - tys := d.m.Types - d.tys[tys.Void] = b.CreateBasicType(llvm.DIBasicType{Name: "void"}) - for _, ty := range []Type{tys.Int, tys.Int8, tys.Int16, tys.Int32, tys.Int64} { - d.tys[ty] = b.CreateBasicType(llvm.DIBasicType{ - Name: ty.TypeName(), - SizeInBits: uint64(ty.SizeInBits()), - Encoding: llvm.DW_ATE_signed, - }) - } - for _, ty := range []Type{tys.Bool, tys.Uint, tys.Uint8, tys.Uint16, tys.Uint32, tys.Uint64, tys.Uintptr, tys.Size} { - d.tys[ty] = b.CreateBasicType(llvm.DIBasicType{ - Name: ty.TypeName(), - SizeInBits: uint64(ty.SizeInBits()), - Encoding: llvm.DW_ATE_unsigned, - }) - } - for _, ty := range []Type{tys.Float32, tys.Float64} { - d.tys[ty] = b.CreateBasicType(llvm.DIBasicType{ - Name: ty.TypeName(), - SizeInBits: uint64(ty.SizeInBits()), - Encoding: llvm.DW_ATE_float, - }) - } - return b -} - -func (d *dbg) finalize() { - if d != nil && d.llvm != nil { - d.llvm.Finalize() - } -} - -// file returns a debug scope for the given file path. -func (d *dbg) file(path string) file { - if existing, ok := d.files[path]; ok { - return existing - } - dir, name := filepath.Split(path) - file := file{path, d.builder().CreateFile(name, dir)} - d.files[path] = file - return file -} - -// ty returns the llvm debug type for the given codegen type t. -func (d *dbg) ty(t Type) (out llvm.Metadata) { - if existing, ok := d.tys[t]; ok { - return existing - } - b := d.builder() - - defer func() { - d.tys[t] = out - }() - - switch t := t.(type) { - case Pointer: - return b.CreatePointerType(llvm.DIPointerType{ - Pointee: d.ty(t.Element), - SizeInBits: uint64(t.SizeInBits()), - AlignInBits: uint32(t.AlignInBits()), - }) - case *Array: - return b.CreateArrayType(llvm.DIArrayType{ - SizeInBits: uint64(t.SizeInBits()), - AlignInBits: uint32(t.AlignInBits()), - ElementType: d.ty(t.Element), - Subscripts: []llvm.DISubrange{{Count: int64(t.Size)}}, - }) - case *FunctionType: - ty := llvm.DISubroutineType{ - // TODO: File - Parameters: make([]llvm.Metadata, len(t.Signature.Parameters)+1), - } - if t.Signature.Result != d.m.Types.Void { - ty.Parameters[0] = d.ty(t.Signature.Result) - } - for i, t := range t.Signature.Parameters { - ty.Parameters[i+1] = d.ty(t) - } - return b.CreateSubroutineType(ty) - case *Struct: - placeholder := b.CreateReplaceableCompositeType(d.cu, llvm.DIReplaceableCompositeType{ - Tag: dwarf.TagStructType, - SizeInBits: uint64(t.SizeInBits()), - AlignInBits: uint32(t.AlignInBits()), - Name: t.TypeName(), - File: d.cu, - }) - d.tys[t] = placeholder - defer func() { placeholder.ReplaceAllUsesWith(out) }() - - fields := t.Fields() - members := make([]llvm.Metadata, len(fields)) - for i, f := range fields { - members[i] = b.CreateMemberType(d.cu, llvm.DIMemberType{ - Name: f.Name, - Type: d.ty(f.Type), - SizeInBits: uint64(f.Type.SizeInBits()), - AlignInBits: uint32(f.Type.AlignInBits()), - OffsetInBits: uint64(t.FieldOffsetInBits(i)), - }) - } - return b.CreateStructType(d.cu, llvm.DIStructType{ - Name: t.TypeName(), - SizeInBits: uint64(t.SizeInBits()), - AlignInBits: uint32(t.AlignInBits()), - Elements: members, - }) - default: - fail("Unhandled type %T", t) - return llvm.Metadata{} - } -} - -// SetLocation sets the source location of the given function. -// SetLocation returns f so it can be called fluently. -func (f *Function) SetLocation(path string, line int) *Function { - d := f.m.dbg - if d == nil { - return f - } - file := d.file(path) - b := d.builder() - dif := b.CreateFunction(file.llvm, llvm.DIFunction{ - Name: f.Name, - LinkageName: f.Name, - File: file.llvm, - Line: line, - Type: d.ty(f.Type), - IsDefinition: true, - }) - f.llvm.SetSubprogram(dif) - f.dbg = &funcDbg{ - scope: dif, - file: file, - declLine: line, - declColumn: 0, - } - return f -} - -// SetLocation sets the source location of the next instructions to be built. -func (b *Builder) SetLocation(line, column int) { - if f := b.function.dbg; f != nil { - b.llvm.SetCurrentDebugLocation(uint(line), uint(column), f.scope, llvm.Metadata{}) - f.curLine, f.curColumn = line, column - } -} - -func (b *Builder) dbgRestoreLocation() { - if d := b.function.dbg; d != nil { - b.SetLocation(d.curLine, d.curColumn) - } -} - -func (b *Builder) dbgEmitParameters(bb llvm.BasicBlock) { - if b.function.dbg == nil { - return - } - d := b.m.dbg.llvm - // Create a debug descriptor for the variable. - for i, param := range b.params { - name := fmt.Sprintf("param_%d", i) - if i < len(b.function.paramNames) { - name = b.function.paramNames[i] - } - loc := llvm.DebugLoc{ - Scope: b.function.dbg.scope, - Line: uint(b.function.dbg.declLine), - Col: uint(b.function.dbg.declColumn), - } - v := d.CreateParameterVariable(b.function.dbg.scope, - llvm.DIParameterVariable{ - Name: name, - File: b.function.dbg.file.llvm, - Line: int(loc.Line), - Type: b.m.dbg.ty(param.Type()), - AlwaysPreserve: true, - ArgNo: 1 + i, - }, - ) - d.InsertValueAtEnd(param.llvm, v, d.CreateExpression(nil), loc, bb) - } -} - -func (b *Builder) dbgEmitValue(val *Value, name string) { - if b.function.dbg == nil { - return - } - d := b.m.dbg.llvm - isAlloca := val.llvm.IsAAllocaInst().C != nil - loc := llvm.DebugLoc{ - Scope: b.function.dbg.scope, - Line: uint(b.function.dbg.curLine), - Col: uint(b.function.dbg.curColumn), - } - elTy := val.Type() - if isAlloca { - elTy = elTy.(Pointer).Element - } - v := d.CreateAutoVariable(b.function.dbg.scope, - llvm.DIAutoVariable{ - Name: name, - File: b.function.dbg.file.llvm, - Line: b.function.dbg.curLine, - Type: b.m.dbg.ty(elTy), - AlignInBits: uint32(elTy.AlignInBits()), - }, - ) - if isAlloca { - d.InsertDeclareAtEnd(val.llvm, v, d.CreateExpression(nil), loc, b.llvm.GetInsertBlock()) - } else { - d.InsertValueAtEnd(val.llvm, v, d.CreateExpression(nil), loc, b.llvm.GetInsertBlock()) - } -} - -type funcDbg struct { - scope llvm.Metadata - file file - declLine, declColumn int - curLine, curColumn int -} - -type file struct { - path string - llvm llvm.Metadata -} diff --git a/core/codegen/doc.go b/core/codegen/doc.go deleted file mode 100644 index fa1358fd9e..0000000000 --- a/core/codegen/doc.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// Package codegen provides an interface for code generation using LLVM. -// -// codegen provides a higher-level and simpler interface for generating AOT or -// JIT code than using the LLVM go bindings directly. -package codegen diff --git a/core/codegen/exceptions.go b/core/codegen/exceptions.go deleted file mode 100644 index aa780c1dc5..0000000000 --- a/core/codegen/exceptions.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package codegen - -type exceptions struct { - allocFn *Function - throwFn *Function - personalityFn *Function - exceptionTy Type - typeInfos map[Type]Global -} - -func (e *exceptions) init(m *Module) { - voidPtr := m.Types.Pointer(m.Types.Void) - - e.exceptionTy = m.Types.TypeOf(struct { - *uint8 - int32 - }{}) - e.personalityFn = m.Function(m.Types.Int32, "__gxx_personality_v0", Variadic) - - // void* __cxa_allocate_exception(size_t size) - e.allocFn = m.Function(voidPtr, "__cxa_allocate_exception", m.Types.Size) - // void __cxa_throw(void *thrown_object, std::type_info *tinfo, void (*destructor)(void *)) { - e.throwFn = m.Function(m.Types.Void, "__cxa_throw", voidPtr, voidPtr, voidPtr) - - e.typeInfos = map[Type]Global{ - m.Types.Int: m.Extern("_ZTIi", voidPtr), - m.Types.Uint32: m.Extern("_ZTIj", voidPtr), - m.Types.Uint64: m.Extern("_ZTIm", voidPtr), - } -} - -func (e *exceptions) throw(b *Builder, v *Value) { - t := b.m.Types - voidPtr := t.Pointer(b.m.Types.Void) - ex := b.Call(e.allocFn, b.Scalar(v.Type().SizeInBits()/8).Cast(t.Size)) - ex.Cast(t.Pointer(v.Type())).Store(v) - ty, ok := e.typeInfos[v.Type()] - if !ok { - fail("Unsuporrted exception type %v", v.Type()) - } - destructor := b.Zero(voidPtr) - b.Call(e.throwFn, ex, ty.Value(b).Cast(voidPtr), destructor) -} diff --git a/core/codegen/function.go b/core/codegen/function.go deleted file mode 100644 index a39e19d7bc..0000000000 --- a/core/codegen/function.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "fmt" - - "llvm/bindings/go/llvm" - - "github.com/google/gapid/core/os/device" -) - -// Function represents a callable function. -type Function struct { - Name string - Type *FunctionType - paramNames []string - llvm llvm.Value - m *Module - dbg *funcDbg - built bool -} - -func (f Function) String() string { - return f.Type.Signature.string(f.Name) -} - -// SetParameterNames assigns debug names to each of the function parameters. -func (f *Function) SetParameterNames(names ...string) *Function { - f.paramNames = names - return f -} - -// Inline makes this function prefer inlining -func (f *Function) Inline() *Function { - kind := llvm.AttributeKindID("alwaysinline") - attr := f.m.ctx.CreateEnumAttribute(kind, 0) - f.llvm.AddFunctionAttr(attr) - return f -} - -// LinkOnceODR sets this function's linkage to "linkonce_odr". This lets the -// function be merged with other global symbols with the same name, with the -// assumption their implementation is identical. Unlike "linkonce" this -// also preserves inlining. -func (f *Function) LinkOnceODR() *Function { - if f.m.target.OS == device.Windows { - c := f.m.llvm.Comdat(f.Name) - c.SetSelectionKind(llvm.AnyComdatSelectionKind) - f.llvm.SetComdat(c) - } - f.llvm.SetLinkage(llvm.WeakODRLinkage) - return f -} - -// LinkPrivate makes this function use private linkage. -func (f *Function) LinkPrivate() *Function { - f.llvm.SetLinkage(llvm.PrivateLinkage) - return f -} - -// LinkInternal makes this function use internal linkage. -func (f *Function) LinkInternal() *Function { - f.llvm.SetLinkage(llvm.InternalLinkage) - return f -} - -// Build calls cb with a Builder that can construct the function body. -func (f *Function) Build(cb func(*Builder)) (err error) { - if f.built { - fail("Function '%v' already built", f.Name) - } - f.built = true - - lb := f.m.ctx.NewBuilder() - defer lb.Dispose() - - entryBlock := f.m.ctx.AddBasicBlock(f.llvm, "entry") - firstExitBlock := f.m.ctx.AddBasicBlock(f.llvm, "exit") - b := &Builder{ - function: f, - params: make([]*Value, len(f.Type.Signature.Parameters)), - entry: entryBlock, - exit: firstExitBlock, // Note: Builder.exit may be updated with chained blocks. - llvm: lb, - m: f.m, - } - - lb.SetInsertPointAtEnd(b.entry) - - for i, p := range f.llvm.Params() { - b.params[i] = b.val(f.Type.Signature.Parameters[i], p) - if i < len(f.paramNames) { - b.params[i].SetName(f.paramNames[i]) - } - } - - b.dbgEmitParameters(b.entry) - - if ty := f.Type.Signature.Result; ty != f.m.Types.Void { - b.result = lb.CreateAlloca(ty.llvmTy(), "result") - lb.CreateStore(llvm.ConstNull(ty.llvmTy()), b.result) - } - - defer func() { - if r := recover(); r != nil { - if failure, ok := r.(buildFailure); ok { - err = fmt.Errorf("%v", string(failure)) - panic(err) // TEMP - } else { - panic(r) // re-throw - } - } - }() - - cb(b) - - if !b.IsBlockTerminated() { - lb.CreateBr(firstExitBlock) - } - - lb.SetInsertPointAtEnd(b.exit) - - if b.result.IsNil() { - lb.CreateRetVoid() - } else { - lb.CreateRet(lb.CreateLoad(b.result, "")) - } - - return nil -} diff --git a/core/codegen/intrinsics.go b/core/codegen/intrinsics.go deleted file mode 100644 index 23886974a5..0000000000 --- a/core/codegen/intrinsics.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -// Memcpy performs an intrinsic memory copy of size bytes from src to dst. -// dst and src must be a void pointer (alias of u8*). -// size is the copy size in bytes. size must be of type uint32. -func (b *Builder) Memcpy(dst, src, size *Value) { - isVolatile := b.Scalar(false) - b.Call(b.m.memcpy, dst, src, size, isVolatile) -} - -// Memset performs an intrinsic memory set to the given value. -// dst must be a void pointer (alias of u8*). -// val is value that is to be repeated size times into dst. val must be of type -// uint8. -// size is the copy size in bytes. size must be of type uint32. -func (b *Builder) Memset(dst, val, size *Value) { - isVolatile := b.Scalar(false) - b.Call(b.m.memset, dst, val, size, isVolatile) -} - -// Memzero performs an intrinsic memory clear to 0. -// dst must be a void pointer (alias of u8*). -// size is the copy size in bytes. size must be of type uint32. -func (b *Builder) Memzero(dst, size *Value) { - b.Memset(dst, b.Scalar(uint8(0)), size) -} diff --git a/core/codegen/module.go b/core/codegen/module.go deleted file mode 100644 index ae3e944c88..0000000000 --- a/core/codegen/module.go +++ /dev/null @@ -1,504 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "bytes" - "fmt" - "reflect" - "regexp" - "strings" - - "github.com/google/gapid/core/os/device" - - "llvm/bindings/go/llvm" -) - -// Module is a JIT module. -type Module struct { - Types Types - llvm llvm.Module - ctx llvm.Context - target *device.ABI - triple Triple - name string - funcs map[string]*Function - strings map[string]llvm.Value - memcpy *Function - memset *Function - exceptions exceptions - dbg *dbg -} - -// NewModule returns a new module with the specified name. -func NewModule(name string, target *device.ABI) *Module { - if target == nil { - panic("NewModule must be passed a non-nil target") - } - layout := target.MemoryLayout - intSize := 8 * int(layout.Integer.Size) - ptrSize := 8 * int(layout.Pointer.Size) - sizeSize := 8 * int(layout.Size.Size) - - triple := TargetTriple(target) - - ctx := llvm.NewContext() - - module := ctx.NewModule(name) - module.SetTarget(triple.String()) - if dl := DataLayout(target); dl != "" { - module.SetDataLayout(dl) - } - - bt := func(name string, dtl *device.DataTypeLayout, llvm llvm.Type) basicType { - return basicType{name, 8 * int(dtl.Size), 8 * int(dtl.Alignment), llvm} - } - - m := &Module{ - Types: Types{ - Void: basicType{"void", 0, 0, ctx.VoidType()}, - Bool: Integer{false, basicType{"bool", 1, 8, ctx.Int1Type()}}, - Int: Integer{true, bt("int", layout.Integer, ctx.IntType(intSize))}, - Int8: Integer{true, bt("int8", layout.I8, ctx.Int8Type())}, - Int16: Integer{true, bt("int16", layout.I16, ctx.Int16Type())}, - Int32: Integer{true, bt("int32", layout.I32, ctx.Int32Type())}, - Int64: Integer{true, bt("int64", layout.I64, ctx.Int64Type())}, - Uint: Integer{false, bt("uint", layout.Integer, ctx.IntType(intSize))}, - Uint8: Integer{false, bt("uint8", layout.I8, ctx.Int8Type())}, - Uint16: Integer{false, bt("uint16", layout.I16, ctx.Int16Type())}, - Uint32: Integer{false, bt("uint32", layout.I32, ctx.Int32Type())}, - Uint64: Integer{false, bt("uint64", layout.I64, ctx.Int64Type())}, - Uintptr: Integer{false, bt("uintptr", layout.Pointer, ctx.IntType(ptrSize))}, - Size: Integer{false, bt("size", layout.Size, ctx.IntType(sizeSize))}, - Float32: Float{bt("float32", layout.F32, ctx.FloatType())}, - Float64: Float{bt("float64", layout.F64, ctx.DoubleType())}, - pointers: map[Type]Pointer{}, - arrays: map[typeInt]*Array{}, - structs: map[string]*Struct{}, - funcs: map[string]*FunctionType{}, - enums: map[string]Enum{}, - aliases: map[string]Alias{}, - named: map[string]Type{}, - }, - llvm: module, - ctx: ctx, - target: target, - triple: triple, - name: name, - funcs: map[string]*Function{}, - strings: map[string]llvm.Value{}, - } - m.Types.emptyStructField = Field{"empty-struct", m.Types.Int8} - m.Types.m = m - - voidPtr := m.Types.Pointer(m.Types.Void) - // void llvm.memcpy.p0i8.p0i8.i32(i8 * , i8 * , i32 , i32 , i1 ) - m.memcpy = m.Function(m.Types.Void, "llvm.memcpy.p0i8.p0i8.i32", - voidPtr, voidPtr, m.Types.Uint32, m.Types.Bool) - // void @llvm.memset.p0i8.i32(i8* , i8 , i32 , i1 ) - m.memset = m.Function(m.Types.Void, "llvm.memset.p0i8.i32", - voidPtr, m.Types.Uint8, m.Types.Uint32, m.Types.Bool) - - m.exceptions.init(m) - - return m -} - -// EmitDebug enables debug info generation for the module. -func (m *Module) EmitDebug() { - if m.dbg == nil { - m.dbg = &dbg{ - files: map[string]file{}, - tys: map[Type]llvm.Metadata{}, - m: m, - } - } -} - -// Verify checks correctness of the module. -func (m *Module) Verify() error { - m.dbg.finalize() - - for f := m.llvm.FirstFunction(); !f.IsNil(); f = llvm.NextFunction(f) { - if err := llvm.VerifyFunction(f, llvm.ReturnStatusAction); err != nil { - f.Dump() - modErr := slashHexToRune(llvm.VerifyModule(m.llvm, llvm.ReturnStatusAction).Error()) - return fmt.Errorf("Function '%s' verification failed:\n%v\n%v", f.Name(), err, modErr) - } - } - - if err := llvm.VerifyModule(m.llvm, llvm.ReturnStatusAction); err != nil { - return fmt.Errorf("Module verification failed:\n%v\n%v", err, m.String()) - } - - return nil -} - -func hex(r rune) byte { - switch { - case r >= '0' && r <= '9': - return byte(r - '0') - case r >= 'A' && r <= 'Z': - return byte(r-'A') + 10 - case r >= 'a' && r <= 'z': - return byte(r-'a') + 10 - default: - return 0 - } -} - -func slashHexToRune(ir string) string { - runes := ([]rune)(ir) - out := bytes.Buffer{} - for { - for len(runes) >= 3 && runes[0] == '\\' { - out.WriteByte(hex(runes[1])<<4 | hex(runes[2])) - runes = runes[3:] - } - if len(runes) == 0 { - break - } - out.WriteRune(runes[0]) - runes = runes[1:] - } - return out.String() -} - -func (m *Module) String() string { - return slashHexToRune(m.llvm.String()) -} - -var parseFuncRE = regexp.MustCompile(`(\w+\s*\**)\s*(\w+)\((.*)\)`) - -// ParseFunctionSignature returns a function parsed from a C-style signature. -// Example: -// "void* Foo(uint8_t i, bool b)" -func (m *Module) ParseFunctionSignature(sig string) *Function { - parts := parseFuncRE.FindStringSubmatch(sig) - if len(parts) != 4 { - fail("'%v' is not a valid function signature", sig) - } - ret := m.parseType(parts[1]) - name := parts[2] - args := m.parseTypes(strings.Split(parts[3], ",")) - return m.Function(ret, name, args...) -} - -func (m *Module) parseTypes(l []string) []Type { - out := make([]Type, len(l)) - for i, s := range l { - out[i] = m.parseType(s) - } - return out -} - -var parseTypeRE = regexp.MustCompile(`^\s*(\w+|\.\.\.)\s*([\*\s]*)`) - -func (m *Module) parseType(s string) Type { - parts := parseTypeRE.FindStringSubmatch(s) - if len(parts) != 3 { - fail("'%v' is not a valid type", s) - } - name := parts[1] - ptrs := parts[2] - - ty := m.parseTypeName(name) - for _, r := range ptrs { - if r == '*' { - ty = m.Types.Pointer(ty) - } - } - return ty -} - -func (m *Module) parseTypeName(name string) Type { - switch name { - case "void": - return m.Types.Void - case "bool": - return m.Types.Bool - case "char": - return m.Types.Uint8 - case "int_t": - return m.Types.Int - case "int8_t": - return m.Types.Int8 - case "int16_t": - return m.Types.Int16 - case "int32_t": - return m.Types.Int32 - case "int64_t": - return m.Types.Int64 - case "uint_t": - return m.Types.Uint - case "uint8_t": - return m.Types.Uint8 - case "uint16_t": - return m.Types.Uint16 - case "uint32_t": - return m.Types.Uint32 - case "uint64_t": - return m.Types.Uint64 - case "float": - return m.Types.Float32 - case "double": - return m.Types.Float64 - case "uintptr_t": - return m.Types.Uintptr - case "...": - return Variadic - default: - if ty, ok := m.Types.named[name]; ok { - return ty - } - fail("'%v' is not a valid type name", name) - return nil - } -} - -// Function creates a new function with the given name, result type and parameters. -func (m *Module) Function(resTy Type, name string, paramTys ...Type) *Function { - ty := m.Types.Function(resTy, paramTys...) - f := llvm.AddFunction(m.llvm, name, ty.llvm) - out := &Function{Name: name, Type: ty, llvm: f, m: m} - if name != "" { - if _, existing := m.funcs[name]; existing { - fail("Duplicate function with name: '%s'", name) - } - m.funcs[name] = out - } - return out -} - -// Ctor creates and builds a new module constructor. -func (m *Module) Ctor(priority int32, cb func(*Builder)) { - ctor := m.Function(m.Types.Void, "ctor") - ctor.Build(cb) - - // TODO: Glob together multiple calls into a single table. - entryTy := m.ctx.StructType([]llvm.Type{ - m.Types.Int32.llvmTy(), - llvm.PointerType(llvm.FunctionType(m.Types.Void.llvmTy(), []llvm.Type{}, false), 0), - }, false) - - entries := []llvm.Value{ - llvm.ConstStruct([]llvm.Value{ - m.Scalar(priority).llvm, - ctor.llvm, - }, false), - } - - table := llvm.ConstArray(entryTy, entries) - g := llvm.AddGlobal(m.llvm, table.Type(), "llvm.global_ctors") - g.SetInitializer(table) - g.SetLinkage(llvm.AppendingLinkage) -} - -// Global is a global value. -type Global struct { - Type Type - llvm llvm.Value -} - -// Value returns a Value for the global. -func (g Global) Value(b *Builder) *Value { - return b.val(g.Type, g.llvm) -} - -// LinkPrivate makes this global use private linkage. -func (g Global) LinkPrivate() Global { - g.llvm.SetLinkage(llvm.PrivateLinkage) - return g -} - -// LinkPublic makes this global use public linkage. -func (g Global) LinkPublic() Global { - g.llvm.SetLinkage(llvm.ExternalLinkage) - return g -} - -// SetConstant makes this global constant. -func (g Global) SetConstant(constant bool) Global { - g.llvm.SetGlobalConstant(constant) - return g -} - -// ZeroGlobal returns a zero-initialized new global variable with the specified -// name and type. -func (m *Module) ZeroGlobal(name string, ty Type) Global { - v := llvm.AddGlobal(m.llvm, ty.llvmTy(), name) - v.SetInitializer(llvm.ConstNull(ty.llvmTy())) - v.SetLinkage(llvm.PrivateLinkage) - return Global{m.Types.Pointer(ty), v} -} - -// Global returns a new global variable intiailized with the specified constant -// value. -func (m *Module) Global(name string, val Const) Global { - v := llvm.AddGlobal(m.llvm, val.Type.llvmTy(), name) - v.SetInitializer(val.llvm) - v.SetLinkage(llvm.PrivateLinkage) - return Global{m.Types.Pointer(val.Type), v} -} - -// Extern returns a global variable declared externally with the given name and -// type. -func (m *Module) Extern(name string, ty Type) Global { - v := llvm.AddGlobal(m.llvm, ty.llvmTy(), name) - return Global{m.Types.Pointer(ty), v} -} - -// Const is an immutable value. -type Const struct { - Type Type - llvm llvm.Value -} - -// Value returns a Value for the constant. -func (c Const) Value(b *Builder) *Value { - return b.val(c.Type, c.llvm) -} - -// Scalar returns an inferred type constant scalar with the value v. -func (m *Module) Scalar(v interface{}) Const { - ty := m.Types.TypeOf(v) - return m.ScalarOfType(v, ty) -} - -// Array returns an constant array with the value v and element type ty. -func (m *Module) Array(v interface{}, elTy Type) Const { - ty := m.Types.Array(elTy, reflect.ValueOf(v).Len()) - return m.ScalarOfType(v, ty) -} - -// ScalarOfType returns a constant scalar with the value v with the given type. -func (m *Module) ScalarOfType(v interface{}, ty Type) Const { - if ty == nil { - fail("ScalarOfType passed nil type") - } - var val llvm.Value - switch v := v.(type) { - case *Value: - val = v.llvm - case Const: - val = v.llvm - case Global: - val = v.llvm - case *Function: - if v != nil { - ty, val = m.Types.Pointer(v.Type), v.llvm - } else { - ty = m.Types.Pointer(m.Types.Void) - val = llvm.ConstNull(ty.llvmTy()) - } - default: - switch reflect.TypeOf(v).Kind() { - case reflect.Array, reflect.Slice: - arrTy, ok := ty.(*Array) - if !ok { - fail("Slice must have an array type. Got %v", ty) - } - rv := reflect.ValueOf(v) - vals := make([]llvm.Value, rv.Len()) - for i := range vals { - vals[i] = m.ScalarOfType(rv.Index(i).Interface(), arrTy.Element).llvm - } - val = llvm.ConstArray(arrTy.Element.llvmTy(), vals) - case reflect.Bool: - if reflect.ValueOf(v).Bool() { - val = llvm.ConstInt(ty.llvmTy(), 1, false) - } else { - val = llvm.ConstInt(ty.llvmTy(), 0, false) - } - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - val = llvm.ConstInt(ty.llvmTy(), uint64(reflect.ValueOf(v).Int()), false) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - val = llvm.ConstInt(ty.llvmTy(), reflect.ValueOf(v).Uint(), false) - case reflect.Float32, reflect.Float64: - val = llvm.ConstFloat(ty.llvmTy(), float64(reflect.ValueOf(v).Float())) - case reflect.Struct: - strTy, ok := ty.(*Struct) - if !ok { - fail("Struct must have an struct type. Got %v", ty) - } - r := reflect.ValueOf(v) - fields := make([]llvm.Value, r.NumField()) - for i := range fields { - fields[i] = m.Scalar(r.Field(i).Interface()).llvm - } - val = llvm.ConstNamedStruct(strTy.llvm, fields) - case reflect.String: - s := v.(string) - var ok bool - val, ok = m.strings[s] - if !ok { - arr := llvm.ConstString(s, true) - buf := llvm.AddGlobal(m.llvm, arr.Type(), "str") - buf.SetInitializer(arr) - buf.SetLinkage(llvm.PrivateLinkage) - ptrTy := m.Types.Pointer(m.Types.Uint8) - val = llvm.ConstPointerCast(buf, ptrTy.llvmTy()) - m.strings[s] = val - } - default: - fail("Scalar does not support type %T", v) - } - } - - if val.Type().C != ty.llvmTy().C { - fail("Value type mismatch for %T: value: %v, type: %v", v, val.Type(), ty.llvmTy()) - } - - val.SetName(fmt.Sprintf("%v", v)) - return Const{ty, val} -} - -// ConstStruct returns a constant struct with the value v. -func (m *Module) ConstStruct(ty *Struct, fields map[string]interface{}) Const { - vals := make([]llvm.Value, len(ty.Fields())) - for i, f := range ty.Fields() { - if v := fields[f.Name]; v == nil { - vals[i] = llvm.ConstNull(f.Type.llvmTy()) - } else { - vals[i] = m.ScalarOfType(v, f.Type).llvm - } - } - val := llvm.ConstNamedStruct(ty.llvm, vals) - return Const{ty, val} -} - -// Zero returns a constant zero value of type ty. -func (m *Module) Zero(ty Type) Const { - return Const{ty, llvm.ConstNull(ty.llvmTy())} -} - -// SizeOf returns the size of the type in bytes as a uint64. -func (m *Module) SizeOf(ty Type) Const { - return m.Scalar(uint64((ty.SizeInBits() + 7) / 8)) -} - -// AlignOf returns the alignment of the type in bytes. -func (m *Module) AlignOf(ty Type) Const { - return m.Scalar(uint64((ty.AlignInBits() + 7) / 8)) -} - -// OffsetOf returns the field offset in bytes. -func (m *Module) OffsetOf(ty *Struct, name string) Const { - idx := ty.FieldIndex(name) - if idx == -1 { - fail("'%v' is not a field of %v", name, ty) - } - return m.Scalar(uint64((ty.FieldOffsetInBits(idx) + 7) / 8)) -} diff --git a/core/codegen/output.go b/core/codegen/output.go deleted file mode 100644 index 25ad175a7a..0000000000 --- a/core/codegen/output.go +++ /dev/null @@ -1,262 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "bytes" - "fmt" - "reflect" - "sort" - "strings" - "unsafe" - - "github.com/google/gapid/core/app/linker" - "github.com/google/gapid/core/os/device" - - "llvm/bindings/go/llvm" -) - -func init() { - llvm.InitializeAllTargetInfos() - llvm.InitializeAllTargets() - llvm.InitializeAllTargetMCs() - llvm.InitializeAllAsmPrinters() -} - -// Executor executes module functions. -type Executor struct { - llvm llvm.ExecutionEngine - funcPtrs map[string]unsafe.Pointer -} - -// Object compiles the module down to an object file. -func (m *Module) Object(optimize bool) ([]byte, error) { - t, err := llvm.GetTargetFromTriple(m.triple.String()) - if err != nil { - return nil, fmt.Errorf("Couldn't get target for triple '%v': %v", m.triple, err) - } - cpu := "" - features := "" - opt := llvm.CodeGenLevelNone - if optimize { - opt = llvm.CodeGenLevelDefault - } - reloc := llvm.RelocPIC - model := llvm.CodeModelDefault - tm := t.CreateTargetMachine(m.triple.String(), cpu, features, opt, reloc, model) - defer tm.Dispose() - - // Check target data is as expected. - td := tm.CreateTargetData() - defer td.Dispose() - m.validateTargetData(td) - - buf, err := tm.EmitToMemoryBuffer(m.llvm, llvm.ObjectFile) - if err != nil { - return nil, err - } - defer buf.Dispose() - return buf.Bytes(), nil -} - -func (m *Module) validateTargetData(td llvm.TargetData) { - abi := m.target - errs := []string{} - check := func(llvm, gapid interface{}, name string) bool { - if reflect.DeepEqual(llvm, gapid) { - return true - } - errs = append(errs, fmt.Sprintf("%v target mismatch for %v: %v (llvm) != %v (gapid)", name, abi.Name, llvm, gapid)) - return false - } - checkTD := func(ty Type, dtl *device.DataTypeLayout) { - check(td.TypeStoreSize(ty.llvmTy()), uint64(dtl.Size), ty.String()+"-size") - check(td.ABITypeAlignment(ty.llvmTy()), int(dtl.Alignment), ty.String()+"-align") - } - - layout := abi.MemoryLayout - isLE := td.ByteOrder() == llvm.LittleEndian - check(isLE, layout.Endian == device.LittleEndian, "is-little-endian") - check(td.PointerSize(), int(layout.Pointer.Size), "pointer-size") - - checkTD(m.Types.Pointer(m.Types.Int), layout.Pointer) - checkTD(m.Types.Int, layout.Integer) - checkTD(m.Types.Size, layout.Size) - checkTD(m.Types.Int64, layout.I64) - checkTD(m.Types.Int32, layout.I32) - checkTD(m.Types.Int16, layout.I16) - checkTD(m.Types.Int8, layout.I8) - checkTD(m.Types.Float32, layout.F32) - checkTD(m.Types.Float64, layout.F64) - - for _, s := range m.Types.structs { - if !s.hasBody { - continue - } - if !check(int(td.TypeStoreSize(s.llvm))*8, s.SizeInBits(), fmt.Sprintf("%v-size", s.name)) || - !check(int(td.ABITypeAlignment(s.llvm))*8, s.AlignInBits(), fmt.Sprintf("%v-align", s.name)) { - errs = append(errs, fmt.Sprintf("%v: %v", s.name, s)) - } - for i := range s.Fields() { - llvm := int(td.ElementOffset(s.llvm, i)) * 8 - gapid := s.FieldOffsetInBits(i) - check(llvm, gapid, fmt.Sprintf("%v-field-offset %d", s.name, i)) - } - } - - for _, s := range m.Types.arrays { - check(int(td.TypeStoreSize(s.llvm))*8, s.SizeInBits(), fmt.Sprintf("%v-size", s.name)) - check(int(td.ABITypeAlignment(s.llvm))*8, s.AlignInBits(), fmt.Sprintf("%v-align", s.name)) - } - - if len(errs) > 0 { - panic(fmt.Errorf("%v has ABI mismatches!\n%v", abi.Name, strings.Join(errs, "\n"))) - } -} - -// Optimize optimizes the module. -func (m *Module) Optimize() { - fpm := llvm.NewFunctionPassManagerForModule(m.llvm) - defer fpm.Dispose() - - mpm := llvm.NewPassManager() - defer mpm.Dispose() - - pmb := llvm.NewPassManagerBuilder() - defer pmb.Dispose() - - pmb.SetOptLevel(int(llvm.CodeGenLevelDefault)) - pmb.SetSizeLevel(0) - - mpm.AddVerifierPass() - fpm.AddVerifierPass() - - pmb.Populate(mpm) - pmb.PopulateFunc(fpm) - - fpm.InitializeFunc() - for fn := m.llvm.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { - fpm.RunFunc(fn) - } - fpm.FinalizeFunc() - - mpm.Run(m.llvm) -} - -// Executor constructs an executor. -func (m *Module) Executor(optimize bool) (*Executor, error) { - m.dbg.finalize() - - if err := m.Verify(); err != nil { - return nil, err - } - - opts := llvm.NewMCJITCompilerOptions() - if optimize { - opts.SetMCJITOptimizationLevel(2) - } else { - opts.SetMCJITOptimizationLevel(0) - } - - engine, err := llvm.NewMCJITCompiler(m.llvm, opts) - if err != nil { - return nil, err - } - - // Check target data is as expected. - m.validateTargetData(engine.TargetData()) - - // Check for unresolved extern symbols. - var unresolved []string - for _, f := range m.funcs { - if f.built || strings.HasPrefix(f.Name, "llvm.") { - continue - } - if linker.ProcAddress(f.Name) == 0 { - unresolved = append(unresolved, fmt.Sprint(f)) - } - } - if len(unresolved) > 0 { - sort.Strings(unresolved) - msg := fmt.Sprintf("Unresolved external functions:\n%v", strings.Join(unresolved, "\n")) - fail(msg) - } - - engine.RunStaticConstructors() - - return &Executor{ - llvm: engine, - funcPtrs: map[string]unsafe.Pointer{}, - }, nil -} - -// FunctionAddress returns the address of the function f. -func (e *Executor) FunctionAddress(f *Function) unsafe.Pointer { - ptr, ok := e.funcPtrs[f.Name] - if !ok { - ptr = e.llvm.PointerToGlobal(f.llvm) - e.funcPtrs[f.Name] = ptr - } - return ptr -} - -// GlobalAddress returns the address of the global g. -func (e *Executor) GlobalAddress(g Global) unsafe.Pointer { - return e.llvm.PointerToGlobal(g.llvm) -} - -// SizeOf returns the offset in bytes between successive objects of the -// specified type, including alignment padding. -func (e *Executor) SizeOf(t Type) int { - return int(e.llvm.TargetData().TypeAllocSize(t.llvmTy())) -} - -// AlignOf returns the preferred stack/global alignment for the specified type. -func (e *Executor) AlignOf(t Type) int { - // TODO: Preferred alignment vs ABI alignment. Which one? - return e.llvm.TargetData().PrefTypeAlignment(t.llvmTy()) -} - -func (e *Executor) FieldOffsets(s *Struct) []int { - td := e.llvm.TargetData() - out := make([]int, len(s.Fields())) - for i := range s.Fields() { - out[i] = int(td.ElementOffset(s.llvm, i)) - } - return out -} - -func (e *Executor) StructLayout(s *Struct) string { - w := bytes.Buffer{} - w.WriteString(s.TypeName()) - w.WriteString("{\n") - e.writeStructLayout(s, &w, 0, "") - w.WriteString("}") - return w.String() -} - -func (e *Executor) writeStructLayout(s *Struct, w *bytes.Buffer, base int, prefix string) { - fields := s.Fields() - for i, o := range e.FieldOffsets(s) { - f := fields[i] - w.WriteString(fmt.Sprintf(" 0x%.4x: ", base+o)) - w.WriteString(prefix) - w.WriteString(f.Name) - w.WriteRune('\n') - if s, ok := f.Type.(*Struct); ok { - e.writeStructLayout(s, w, base+o, prefix+f.Name+".") - } - } -} diff --git a/core/codegen/triple.go b/core/codegen/triple.go deleted file mode 100644 index e7237261c8..0000000000 --- a/core/codegen/triple.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "strings" - - "github.com/google/gapid/core/os/device" -) - -// TargetTriple returns an LLVM target triple for the given ABI in the form: -// --- -// -// References: -// https://github.com/llvm-mirror/llvm/blob/master/lib/Support/Triple.cpp -// https://clang.llvm.org/docs/CrossCompilation.html -func TargetTriple(dev *device.ABI) Triple { - out := Triple{"unknown", "unknown", "unknown", "unknown"} - // Consult Triple.cpp for legal values for each of these. - // arch: parseArch() + parseSubArch() - // vendor: parseVendor() - // os: parseOS() - // abi: parseEnvironment() + parseFormat() - - switch dev.Architecture { - case device.ARMv7a: - out.arch = "armv7" - case device.ARMv8a: - out.arch = "aarch64" - case device.X86: - out.arch = "i386" - case device.X86_64: - out.arch = "x86_64" - } - - switch dev.OS { - case device.Windows: - out.vendor, out.os, out.abi = "w64", "windows", "gnu" - case device.OSX: - out.vendor, out.os = "apple", "darwin" - case device.Linux: - out.os = "linux" - case device.Stadia: - out.os = "linux" - case device.Android: - out.os, out.abi = "linux", "androideabi" - } - - return out -} - -// Triple represents an LLVM target triple. -type Triple struct { - arch, vendor, os, abi string -} - -// NewTriple returns a new Triple. -func NewTriple(arch, vendor, os, abi string) Triple { - return Triple{arch, vendor, os, abi} -} - -func (t Triple) String() string { - return strings.Join([]string{t.arch, t.vendor, t.os, t.abi}, "-") -} diff --git a/core/codegen/triple_test.go b/core/codegen/triple_test.go deleted file mode 100644 index 753da27cfe..0000000000 --- a/core/codegen/triple_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen_test - -import ( - "testing" - - "github.com/google/gapid/core/assert" - "github.com/google/gapid/core/codegen" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/os/device" -) - -func TestTargetTriple(t *testing.T) { - ctx := log.Testing(t) - for _, t := range []struct { - name string - abi *device.ABI - expected codegen.Triple - }{ - {"win-x64", device.WindowsX86_64, codegen.NewTriple("x86_64", "w64", "windows", "gnu")}, - {"osx-x64", device.OSXX86_64, codegen.NewTriple("x86_64", "apple", "darwin", "unknown")}, - {"linux-x64", device.LinuxX86_64, codegen.NewTriple("x86_64", "unknown", "linux", "unknown")}, - {"android-arm64", device.AndroidARM64v8a, codegen.NewTriple("aarch64", "unknown", "linux", "androideabi")}, - {"android-armv7a", device.AndroidARMv7a, codegen.NewTriple("armv7", "unknown", "linux", "androideabi")}, - {"android-x86", device.AndroidX86, codegen.NewTriple("i386", "unknown", "linux", "androideabi")}, - {"android-x64", device.AndroidX86_64, codegen.NewTriple("x86_64", "unknown", "linux", "androideabi")}, - } { - assert.For(ctx, t.name).That(codegen.TargetTriple(t.abi)).Equals(t.expected) - } -} diff --git a/core/codegen/types.go b/core/codegen/types.go deleted file mode 100644 index cc8a073808..0000000000 --- a/core/codegen/types.go +++ /dev/null @@ -1,714 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "bytes" - "fmt" - "reflect" - "strings" - - "llvm/bindings/go/llvm" - - "github.com/google/gapid/core/math/sint" -) - -// SizeOf returns the size of the type in bytes as a uint64. -// If ty is void, a value of 1 is returned. -func (b *Builder) SizeOf(ty Type) *Value { - return b.m.SizeOf(ty).Value(b). - SetName(fmt.Sprintf("sizeof(%v)", ty.TypeName())) -} - -// AlignOf returns the alignment of the type in bytes. -func (b *Builder) AlignOf(ty Type) *Value { - return b.m.AlignOf(ty).Value(b). - SetName(fmt.Sprintf("alignof(%v)", ty.TypeName())) -} - -// StrideInBits returns the number of bits per element when held in an array. -func StrideInBits(ty Type) int { - return sint.AlignUp(sint.Max(ty.SizeInBits(), 8), ty.AlignInBits()) -} - -// Types contains all the types for a module. -type Types struct { - m *Module - - Void Type // Void is the void type. - Bool Type // Bool is a one-bit integer type. - Int Type // Int is a signed 32-bit or 64-bit integer type. - Int8 Type // Int8 is a signed 8-bit integer type. - Int16 Type // Int16 is a signed 16-bit integer type. - Int32 Type // Int32 is a signed 32-bit integer type. - Int64 Type // Int64 is a signed 64-bit integer type. - Uint Type // Uint is an unsigned 32-bit or 64-bit integer type. - Uint8 Type // Uint8 is an unsigned 8-bit integer type. - Uint16 Type // Uint16 is an unigned 16-bit integer type. - Uint32 Type // Uint32 is an unsigned 32-bit integer type. - Uint64 Type // Uint64 is an unsigned 64-bit integer type. - Uintptr Type // Uinptr is an unsigned integer type of the same width as a pointer. - Size Type // Size is an unsigned integer of native bit-width. - Float32 Type // Float32 is a 32-bit floating-point number type. - Float64 Type // Float64 is a 64-bit floating-point number type. - - pointers map[Type]Pointer // T -> T* - arrays map[typeInt]*Array - structs map[string]*Struct - funcs map[string]*FunctionType - enums map[string]Enum - aliases map[string]Alias - named map[string]Type - - // emptyStructField is a field that is placed into empty structs so they - // are addressable. This is also automatically done by LLVM. - emptyStructField Field -} - -type typeInt struct { - Type - int -} - -// Type represents a codegen type. -type Type interface { - String() string - TypeName() string - - SizeInBits() int - AlignInBits() int - llvmTy() llvm.Type -} - -// TypeList is a slice of types. -type TypeList []Type - -func (l TypeList) String() string { - parts := make([]string, len(l)) - for i, p := range l { - parts[i] = p.TypeName() - } - return strings.Join(parts, ", ") -} - -func (l TypeList) llvm() []llvm.Type { - out := make([]llvm.Type, len(l)) - for i, t := range l { - out[i] = t.llvmTy() - } - return out -} - -type basicType struct { - name string - sizeInBits int - alignInBits int - llvm llvm.Type -} - -func (t basicType) TypeName() string { return t.name } -func (t basicType) String() string { return t.name } -func (t basicType) llvmTy() llvm.Type { return t.llvm } -func (t basicType) SizeInBits() int { return t.sizeInBits } -func (t basicType) AlignInBits() int { return t.alignInBits } - -// Pointer represents a pointer type. -type Pointer struct { - Element Type // The type of the element the pointer points to. - - basicType -} - -// Pointer returns a pointer type of el. -func (t *Types) Pointer(el Type) Pointer { - p, ok := t.pointers[el] - if !ok { - if el == t.Void { - el = t.Uint8 - } - p = Pointer{el, basicType{ - fmt.Sprintf("*%v", el.TypeName()), - int(t.m.target.MemoryLayout.Pointer.Size * 8), - int(t.m.target.MemoryLayout.Pointer.Alignment * 8), - llvm.PointerType(el.llvmTy(), 0), - }} - t.pointers[el] = p - } - return p -} - -// Array represents a static array type. -type Array struct { - Element Type // The type of the element the pointer points to. - Size int // Number of elements in the array. - name string - llvm llvm.Type -} - -func (t Array) TypeName() string { return t.name } -func (t Array) String() string { return t.name } -func (t Array) llvmTy() llvm.Type { return t.llvm } -func (t Array) SizeInBits() int { return StrideInBits(t.Element) * t.Size } -func (t Array) AlignInBits() int { return t.Element.AlignInBits() } - -// Array returns an n-element array type of el. -func (t *Types) Array(el Type, n int) *Array { - a, ok := t.arrays[typeInt{el, n}] - if !ok { - a = &Array{ - Element: el, - Size: n, - name: fmt.Sprintf("%v[%d]", el.TypeName(), n), - llvm: llvm.ArrayType(el.llvmTy(), n), - } - t.arrays[typeInt{el, n}] = a - } - return a -} - -// IsPointer returns true if ty is a pointer type. -func IsPointer(ty Type) bool { - _, ok := ty.(Pointer) - return ok -} - -// Vector represents a vector type. -type Vector struct { - Element Type // The type of the vector elements. - Count int // Number of elements in a vector. - basicType -} - -// Vector returns a pointer type of el. -func (t *Types) Vector(el Type, count int) Vector { - return Vector{el, count, basicType{ - fmt.Sprintf("vec<%v, %d>", el, count), - el.SizeInBits() * count, - el.AlignInBits(), - llvm.VectorType(el.llvmTy(), count), - }} -} - -// IsVector returns true if ty is a vector type. -func IsVector(ty Type) bool { - _, ok := ty.(Vector) - return ok -} - -// Scalar returns the element type if ty is a vector, otherwise it returns -// ty. -func Scalar(ty Type) Type { - if vec, ok := ty.(Vector); ok { - return vec.Element - } - return ty -} - -// Integer represents an integer type. -type Integer struct { - Signed bool // Is this integer type signed? - - basicType -} - -// IsBool returns true if ty is the boolean type. -func IsBool(ty Type) bool { - t, ok := ty.(basicType) - return ok && t.llvm.IntTypeWidth() == 1 -} - -// IsInteger returns true if ty is an integer type. -func IsInteger(ty Type) bool { - _, ok := ty.(Integer) - return ok -} - -// IsEnum returns true if ty is an enum type. -func IsEnum(ty Type) bool { - _, ok := ty.(Enum) - return ok -} - -// IsSignedInteger returns true if ty is a signed integer type. -func IsSignedInteger(ty Type) bool { - i, ok := ty.(Integer) - return ok && i.Signed -} - -// IsUnsignedInteger returns true if ty is an unsigned integer type. -func IsUnsignedInteger(ty Type) bool { - i, ok := ty.(Integer) - return ok && !i.Signed -} - -// IsIntegerOrEnum returns true if ty is an integer or enum type. -func IsIntegerOrEnum(ty Type) bool { return IsInteger(ty) || IsEnum(ty) } - -// IsSignedIntegerOrEnum returns true if ty is a signed integer or enum type. -func IsSignedIntegerOrEnum(ty Type) bool { return IsSignedInteger(ty) || IsEnum(ty) } - -// Float represents a floating-point type. -type Float struct { - basicType -} - -func (t Float) TypeName() string { return t.name } -func (t Float) String() string { return t.name } - -// IsFloat returns true if ty is a float type. -func IsFloat(ty Type) bool { - _, ok := ty.(Float) - return ok -} - -// FunctionType is the type of a function -type FunctionType struct { - Signature Signature - llvm llvm.Type -} - -func (t FunctionType) TypeName() string { return t.Signature.string("") } -func (t FunctionType) String() string { return t.Signature.string("") } -func (t FunctionType) SizeInBits() int { return 0 } -func (t FunctionType) AlignInBits() int { return 0 } -func (t FunctionType) llvmTy() llvm.Type { return t.llvm } - -// IsFunction returns true if ty is a function type. -func IsFunction(ty Type) bool { - _, ok := ty.(*FunctionType) - return ok -} - -// Signature holds signature information about a function. -type Signature struct { - Parameters TypeList - Result Type - Variadic bool -} - -func (s Signature) string(name string) string { - return fmt.Sprintf("%v %v(%v)", s.Result, name, s.Parameters) -} - -func (s Signature) key() string { - parts := make([]string, len(s.Parameters)) - for i, p := range s.Parameters { - parts[i] = fmt.Sprint(p) - } - if s.Variadic { - return fmt.Sprintf("(%v, ...)%v", s.Parameters, s.Result) - } - return fmt.Sprintf("(%v)%v", s.Parameters, s.Result) -} - -type variadicTy struct{} - -func (variadicTy) String() string { return "..." } -func (variadicTy) TypeName() string { return "..." } -func (variadicTy) SizeInBits() int { panic("Cannot use Variadic as a regular type") } -func (variadicTy) AlignInBits() int { panic("Cannot use Variadic as a regular type") } -func (variadicTy) llvmTy() llvm.Type { panic("Cannot use Variadic as a regular type") } - -// Variadic is a type that can be used as the last parameter of a function -// definition to indicate a variadic function. -var Variadic variadicTy - -// Struct is the type of a structure. -type Struct struct { - t *Types - name string - fields []Field - fieldIndices map[string]int - packed bool - hasBody bool - layout *structLayout - llvm llvm.Type -} - -type structLayout struct { - offsets []int - sizeInBits int - alignInBits int -} - -func (t *Struct) getStructLayout() *structLayout { - if t.layout != nil { - if len(t.layout.offsets) != len(t.fields) { - fail("Field count mismatch between struct (%v) and layout (%v) for '%v'", - len(t.fields), len(t.layout.offsets), t.name) - } - return t.layout - } - if !t.hasBody { - fail("Attempting to get struct '%v' layout before it has a body", t.name) - } - offsets := make([]int, len(t.fields)) - offset, align := 0, 8 - for i, f := range t.fields { - if t.packed { - offsets[i] = offset - offset += StrideInBits(f.Type) - } else { - a := f.Type.AlignInBits() - offset = sint.AlignUp(offset, a) - offsets[i] = offset - offset += StrideInBits(f.Type) - align = sint.Max(align, a) - } - } - offset = sint.AlignUp(offset, align) - t.layout = &structLayout{ - offsets: offsets, - sizeInBits: offset, - alignInBits: align, - } - return t.layout -} - -func (t *Struct) TypeName() string { return t.name } -func (t *Struct) String() string { - if len(t.fields) == 0 { - return fmt.Sprintf("%v{}", t.name) - } - b := bytes.Buffer{} - b.WriteString(t.name) - b.WriteString(" {") - for _, f := range t.fields { - b.WriteString("\n ") - b.WriteString(f.Name) - b.WriteString(": ") - b.WriteString(f.Type.TypeName()) - } - b.WriteString("\n}") - return b.String() -} -func (t *Struct) Fields() []Field { return t.fields } -func (t *Struct) SizeInBits() int { return t.getStructLayout().sizeInBits } -func (t *Struct) AlignInBits() int { return t.getStructLayout().alignInBits } -func (t *Struct) llvmTy() llvm.Type { return t.llvm } - -// Field returns the field with the given name. -// Field panics if the struct does not contain the given field. -func (t *Struct) Field(name string) Field { - f, ok := t.fieldIndices[name] - if !ok { - fail("Struct '%v' does not have field with name '%v'", t.name, name) - } - return t.fields[f] -} - -// FieldIndex returns the index of the field with the given name, or -1 if the -// struct does not have a field with the given name. -func (t *Struct) FieldIndex(name string) int { - f, ok := t.fieldIndices[name] - if !ok { - return -1 - } - return f -} - -// FieldOffsetInBits returns the field offset in bits from the start of the struct. -func (t *Struct) FieldOffsetInBits(idx int) int { return t.getStructLayout().offsets[idx] } - -// IsStruct returns true if ty is a struct type. -func IsStruct(ty Type) bool { - _, ok := ty.(*Struct) - return ok -} - -// Field is a single field in a struct. -type Field struct { - Name string - Type Type -} - -// struct_ creates a new struct populated with the given fields. -// If packed is true then fields will be stored back-to-back. -func (t *Types) struct_(name string, packed bool, fields []Field) *Struct { - name = sanitizeStructName(name) - if fields != nil && len(fields) == 0 { - fields = []Field{t.emptyStructField} - } - if s, ok := t.structs[name]; ok { - if fields != nil { - if !reflect.DeepEqual(fields, s.fields) { - fail("Struct '%s' redeclared with different fields\nPrevious: %+v\nNew: %+v", - name, s.fields, fields) - } - if packed != s.llvm.IsStructPacked() { - fail("Struct '%s' redeclared with different packed flags", name) - } - } - return s - } - - ty := t.m.ctx.StructCreateNamed(name) - s := &Struct{ - t: t, - name: name, - packed: packed, - llvm: ty, - } - t.registerNamed(s) - if fields != nil { - s.SetBody(packed, fields...) - s.hasBody = true - } - if name != "" { - t.structs[name] = s - } - return s -} - -func (t *Types) registerNamed(ty Type) { - if name := ty.TypeName(); name != "" { - if _, dup := t.named[name]; dup { - fail("Duplicate types with the name %v", name) - } - t.named[name] = ty - } -} - -// SetBody sets the fields of the declared struct. -func (t *Struct) SetBody(packed bool, fields ...Field) *Struct { - if t.hasBody && !reflect.DeepEqual(fields, t.fields) { - fail("Attempting to change fields of struct '%v'\nOld: %+v\nNew: %+v", t.name, t.fields, fields) - } - indices := map[string]int{} - if len(fields) == 0 { - fields = []Field{t.t.emptyStructField} - } - l := make([]llvm.Type, len(fields)) - for i, f := range fields { - if f.Type == nil { - fail("Field '%s' (%d) has nil type", f.Name, i) - } - l[i] = f.Type.llvmTy() - indices[f.Name] = i - } - t.fields = fields - t.fieldIndices = indices - t.hasBody = true - t.llvm.StructSetBody(l, packed) - return t -} - -// Alias is a named type that aliases another type. -type Alias struct { - name string - to Type -} - -func (a Alias) String() string { return fmt.Sprintf("%v (%v)", a.TypeName(), Underlying(a).TypeName()) } -func (a Alias) TypeName() string { return a.name } -func (a Alias) SizeInBits() int { return a.to.SizeInBits() } -func (a Alias) AlignInBits() int { return a.to.AlignInBits() } -func (a Alias) llvmTy() llvm.Type { return a.to.llvmTy() } - -// Alias creates a new alias type. -func (t *Types) Alias(name string, to Type) Alias { - ty := Alias{name: name, to: to} - t.aliases[name] = ty - t.registerNamed(ty) - return ty -} - -// Underlying returns the underlying non-aliased type for ty. -func Underlying(ty Type) Type { - for { - if a, ok := ty.(Alias); ok { - ty = a.to - } else { - return ty - } - } -} - -// Enum is an enumerator. -type Enum struct{ basicType } - -// Enum creates a new enum type. -func (t *Types) Enum(name string) Enum { - ty := Enum{ - basicType{ - name: name, - sizeInBits: t.Int.SizeInBits(), - alignInBits: t.Int.AlignInBits(), - llvm: t.Int.llvmTy(), - }, - } - t.enums[name] = ty - t.registerNamed(ty) - return ty -} - -// DeclareStruct creates a new, empty struct type. -func (t *Types) DeclareStruct(name string) *Struct { - return t.struct_(name, false, nil) -} - -// DeclarePackedStruct creates a new, packed empty struct type. -func (t *Types) DeclarePackedStruct(name string) *Struct { - return t.struct_(name, true, nil) -} - -// Struct creates a new unpacked struct type. -func (t *Types) Struct(name string, fields ...Field) *Struct { - return t.struct_(name, false, fields) -} - -// PackedStruct creates a new packed struct type. -func (t *Types) PackedStruct(name string, fields ...Field) *Struct { - return t.struct_(name, true, fields) -} - -// TypeOf returns the corresponding codegen type for the type of value v. -// TypeOf may also accept a reflect.Type, Type, *Function or Const. -func (t *Types) TypeOf(v interface{}) Type { - var ty reflect.Type - switch v := v.(type) { - case Type: - return v - case *Value: - return v.Type() - case *Function: - if v != nil { - return v.Type - } - case Const: - return v.Type - case reflect.Type: - ty = v - } - - if ty == nil { - ty = reflect.TypeOf(v) - } - - if ty == reflect.TypeOf((*Function)(nil)) { - // We have a reflect type of a function, but we don't have a value to - // inspect its type. Return void* for these. - return t.Pointer(t.Void) - } - - switch ty.Kind() { - case reflect.Bool: - return t.Bool - case reflect.Int: - return t.Int - case reflect.Int8: - return t.Int8 - case reflect.Int16: - return t.Int16 - case reflect.Int32: - return t.Int32 - case reflect.Int64: - return t.Int64 - case reflect.Uint: - return t.Uint - case reflect.Uint8: - return t.Uint8 - case reflect.Uint16: - return t.Uint16 - case reflect.Uint32: - return t.Uint32 - case reflect.Uint64: - return t.Uint64 - case reflect.Float32: - return t.Float32 - case reflect.Float64: - return t.Float64 - case reflect.Ptr: - return t.Pointer(t.TypeOf(ty.Elem())) - case reflect.Interface: - return t.TypeOf(ty.Elem()) - case reflect.UnsafePointer, reflect.Uintptr: - return t.Pointer(t.Uint8) - case reflect.Array: - return t.Array(t.TypeOf(ty.Elem()), ty.Len()) - case reflect.Slice: - return t.Array(t.TypeOf(ty.Elem()), reflect.ValueOf(v).Len()) - case reflect.String: - return t.Pointer(t.Uint8) - case reflect.Struct: - name := sanitizeStructName(ty.Name()) - if s, ok := t.structs[name]; ok { - return s // avoid stack overflow if type references itself. - } - s := t.DeclareStruct(name) - if !s.hasBody { - fields := t.FieldsOf(ty) - s.SetBody(false, fields...) - } - return s - default: - fail("Unsupported kind %v", ty.Kind()) - return nil - } -} - -// FieldsOf returns the codegen fields of the given struct type. -// FieldsOf may also accept a reflect.Type. -func (t *Types) FieldsOf(v interface{}) []Field { - ty, ok := v.(reflect.Type) - if !ok { - ty = reflect.TypeOf(v) - } - if ty.Kind() != reflect.Struct { - fail("FieldsOf must be passed a struct type. Got %v", ty) - } - c := ty.NumField() - fields := make([]Field, 0, c) - for i := 0; i < c; i++ { - f := ty.Field(i) - if f.Name == "_" { - continue // Cgo padding struct. No thanks. - } - fields = append(fields, Field{Name: f.Name, Type: t.TypeOf(f.Type)}) - } - return fields -} - -// sanitizeStructName removes cgo mangling from the struct name. -func sanitizeStructName(name string) string { - if strings.HasPrefix(name, "_Ctype") { - name = strings.TrimPrefix(name, "_Ctype_struct_") // Remove Cgo prefix... - name = strings.TrimSuffix(name, "_t") // ... and '_t' - } - return name -} - -// Function returns a type representing the given function signature. -func (t *Types) Function(resTy Type, paramTys ...Type) *FunctionType { - if resTy == nil { - resTy = t.Void - } - params, variadic := TypeList(paramTys), false - if len(params) > 0 && params[len(params)-1] == Variadic { - params, variadic = params[:len(params)-1], true - } - sig := Signature{params, resTy, variadic} - key := sig.key() - ty, ok := t.funcs[key] - if ok { - return ty - } - ty = &FunctionType{ - sig, - llvm.FunctionType(resTy.llvmTy(), params.llvm(), variadic), - } - t.funcs[key] = ty - return ty -} diff --git a/core/codegen/value.go b/core/codegen/value.go deleted file mode 100644 index 5d859d11a1..0000000000 --- a/core/codegen/value.go +++ /dev/null @@ -1,247 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package codegen - -import ( - "fmt" - "strings" - - "llvm/bindings/go/llvm" -) - -func (b *Builder) val(ty Type, val llvm.Value) *Value { - // if a, b := val.Type().String(), ty.llvmTy().String(); a != b { - // fail("Value type mismatch: value: %v, type: %v", a, b) - // } - return &Value{ty, val, val.Name(), b} -} - -// Value represents a value. -type Value struct { - ty Type - llvm llvm.Value - name string - b *Builder -} - -// Type returns the type of the value. -func (v *Value) Type() Type { return v.ty } - -// Name returns the value's name. -func (v *Value) Name() string { return v.name } - -// SetName assigns a name to the value. -func (v *Value) SetName(name string) *Value { - v.name = name - if IsPointer(v.ty) { - name = "→" + name - } - v.llvm.SetName(name) - return v -} - -// EmitDebug emits debug info for the value. -func (v *Value) EmitDebug(name string) *Value { - v.b.dbgEmitValue(v, name) - return v -} - -// Load loads the element from the pointer value. -func (v *Value) Load() *Value { - if !IsPointer(v.ty) { - fail("Load must be from a pointer. Got %v", v.Type()) - } - elTy := v.Type().(Pointer).Element - return v.b.val(elTy, v.b.llvm.CreateLoad(v.llvm, v.name)) -} - -// LoadUnaligned loads the element from the pointer value, it allows the loaded -// value to be unaligned -func (v *Value) LoadUnaligned() *Value { - if !IsPointer(v.ty) { - fail("Load must be from a pointer. Got %v", v.Type) - } - elTy := v.Type().(Pointer).Element - load := v.b.llvm.CreateLoad(v.llvm, v.name) - load.SetAlignment(1) - return v.b.val(elTy, load) -} - -// StoreUnaligned stores val to the pointer ptr. It assumes that the -// destination address may be unaligned -func (v *Value) StoreUnaligned(val *Value) { - if !IsPointer(v.ty) { - fail("Store must be to a pointer. Got %v", v.Type) - } - elTy := v.Type().(Pointer).Element - if val.ty.String() != elTy.String() { - fail("Attempted to store value of type %v to pointer element type %v", - val.ty.TypeName(), elTy.TypeName()) - } - store := v.b.llvm.CreateStore(val.llvm, v.llvm) - store.SetAlignment(1) -} - -// Store stores val to the pointer ptr. -func (v *Value) Store(val *Value) { - if !IsPointer(v.ty) { - fail("Store must be to a pointer. Got %v", v.Type) - } - elTy := v.Type().(Pointer).Element - if val.ty.String() != elTy.String() { - fail("Attempted to store value of type %v to pointer element type %v", - val.ty.TypeName(), elTy.TypeName()) - } - v.b.llvm.CreateStore(val.llvm, v.llvm) -} - -func field(s *Struct, f IndexOrName) (Field, int) { - var i int - switch f := f.(type) { - case int: - i = f - case string: - var ok bool - if i, ok = s.fieldIndices[f]; !ok { - fail("%v does not contain a field with name '%v':\n%+v", s.TypeName(), f, s) - } - default: - fail("Attempted to index field of struct '%v' with %T. Must be int or string", s.TypeName(), f) - } - return s.fields[i], i -} - -func (b *Builder) pathName(rootName string, path []ValueIndexOrName) string { - name := rootName - for _, p := range path { - switch p := p.(type) { - case int: - name = fmt.Sprintf("%v[%v]", name, p) - case string: - name = fmt.Sprintf("%v.%v", name, p) - case *Value: - name = fmt.Sprintf("%v[%v]", name, p.Name()) - } - } - return name -} - -func (b *Builder) path(rootTy Type, rootName string, path ...ValueIndexOrName) (indices []llvm.Value, name string, target Type) { - err := func(i int) string { - full := b.pathName(rootName, path) - okay := b.pathName(rootName, path[:i]) - fail := b.pathName(rootName, path[:i+1]) - pad := strings.Repeat(" ", len(okay)) - highlight := strings.Repeat("^", len(fail)-len(okay)) - return fmt.Sprintf("\n%v\n%v%v", full, pad, highlight) - } - target = rootTy - indices = make([]llvm.Value, len(path)) - for i, p := range path { - switch t := target.(type) { - case Pointer: - if i == 0 { - switch p := p.(type) { - case int: - indices[i] = b.Scalar(uint32(p)).llvm - case *Value: - if !IsInteger(p.Type()) { - fail("Tried to index pointer with non-integer %v.%v", p.Type().TypeName(), err(i)) - } - indices[i] = p.llvm - default: - fail("Tried to index pointer with %T (%v).%v", p, err(i)) - } - target = t.Element - } else { - fail("Tried to index %v. Only the root pointer can be indexed.%v", target.TypeName(), err(i)) - } - case *Struct: - field, idx := field(t, p) - target = field.Type - indices[i] = b.Scalar(uint32(idx)).llvm - case *Array: - switch p := p.(type) { - case int: - indices[i] = b.Scalar(uint32(p)).llvm - case *Value: - indices[i] = p.llvm - default: - fail("Tried to index array with %T.%v", p, err(i)) - } - target = t.Element - default: - fail("Cannot index type %v.%v", target, err(i)) - } - } - return indices, b.pathName(rootName, path), target -} - -// Index returns a new pointer to the array or field element found by following -// the list of indices as specified by path. -func (v *Value) Index(path ...ValueIndexOrName) *Value { - if !IsPointer(v.ty) { - fail("Index only works with pointer value types. Got %v", v.Type().TypeName()) - } - indices, name, target := v.b.path(v.Type(), v.Name(), path...) - return v.b.val(v.b.m.Types.Pointer(target), v.b.llvm.CreateGEP(v.llvm, indices, "")).SetName(name) -} - -// Insert creates a copy of the struct or array v with the field/element at -// changed to val. -func (v *Value) Insert(at ValueIndexOrName, val *Value) *Value { - switch ty := v.ty.(type) { - case *Struct: - f, idx := field(ty, at) - assertTypesEqual(f.Type, val.Type()) - return v.b.val(ty, v.b.llvm.CreateInsertValue(v.llvm, val.llvm, idx, "")).SetName(v.Name()) - case *Array: - idx, ok := at.(int) - if !ok { - fail("Insert parameter at must be int for arrays values. Got %T", at) - } - assertTypesEqual(ty.Element, val.Type()) - return v.b.val(ty, v.b.llvm.CreateInsertValue(v.llvm, val.llvm, idx, "")).SetName(v.Name()) - default: - fail("Attempted to insert on non-struct and non-array type %v", v.ty.TypeName()) - return nil - } -} - -// Extract returns the field at extracted from the struct or array v. -func (v *Value) Extract(at IndexOrName) *Value { - switch ty := v.ty.(type) { - case *Struct: - f, idx := field(ty, at) - return v.b.val(f.Type, v.b.llvm.CreateExtractValue(v.llvm, idx, f.Name)) - case *Array: - idx, ok := at.(int) - if !ok { - fail("Extract parameter at must be int for arrays values. Got %T", at) - } - return v.b.val(ty.Element, v.b.llvm.CreateExtractValue(v.llvm, idx, fmt.Sprintf("%v[%d]", v.name, idx))) - default: - fail("Attempted to extract on non-struct and non-array type %v", v.ty.TypeName()) - return nil - } -} - -// IsNull returns true if the pointer value v is null. -func (v *Value) IsNull() *Value { - if !IsPointer(v.ty) { - fail("IsNull only works with pointer value types. Got %v", v.Type().TypeName()) - } - return v.b.val(v.b.m.Types.Bool, v.b.llvm.CreateIsNull(v.llvm, "")) -} diff --git a/core/context/keys/BUILD.bazel b/core/context/keys/BUILD.bazel index 63f5344604..d4c0eb72a7 100644 --- a/core/context/keys/BUILD.bazel +++ b/core/context/keys/BUILD.bazel @@ -29,5 +29,5 @@ go_test( name = "go_default_test", size = "small", srcs = ["keys_test.go"], - embed = [":go_default_library"], + deps = [":go_default_library"], ) diff --git a/core/data/BUILD.bazel b/core/data/BUILD.bazel index 2385c11fe3..693f2a274a 100644 --- a/core/data/BUILD.bazel +++ b/core/data/BUILD.bazel @@ -28,8 +28,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["dedupe_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/data/binary/BUILD.bazel b/core/data/binary/BUILD.bazel index 0697f8aca3..385a15a3cd 100644 --- a/core/data/binary/BUILD.bazel +++ b/core/data/binary/BUILD.bazel @@ -32,6 +32,8 @@ go_library( go_test( name = "go_default_test", srcs = ["bitstream_test.go"], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/data/compare/BUILD.bazel b/core/data/compare/BUILD.bazel index 1fb898976f..c827c2d6ac 100644 --- a/core/data/compare/BUILD.bazel +++ b/core/data/compare/BUILD.bazel @@ -35,6 +35,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["compare_test.go"], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/data/deep/BUILD.bazel b/core/data/deep/BUILD.bazel index 37e79f8ae3..9c64f3b3d0 100644 --- a/core/data/deep/BUILD.bazel +++ b/core/data/deep/BUILD.bazel @@ -28,8 +28,8 @@ go_library( go_test( name = "go_default_test", srcs = ["copy_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/data:go_default_library", "//core/log:go_default_library", diff --git a/core/data/dictionary/BUILD.bazel b/core/data/dictionary/BUILD.bazel index 3a33448f0a..7f1414eadd 100644 --- a/core/data/dictionary/BUILD.bazel +++ b/core/data/dictionary/BUILD.bazel @@ -31,8 +31,8 @@ go_library( go_test( name = "go_default_test", srcs = ["dictionary_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/data/slice:go_default_library", "//core/log:go_default_library", diff --git a/core/data/dictionary/source.go b/core/data/dictionary/source.go index a433214f2c..6714101ae5 100644 --- a/core/data/dictionary/source.go +++ b/core/data/dictionary/source.go @@ -33,8 +33,8 @@ var ( vTy = generic.T2Ty ) -// Source is a dummy interface used to prototype a generic dictionary type. -// K and V can be subsituted with a different type. +// Source is a placeholder interface used to prototype a generic dictionary +// type. K and V can be subsituted with a different type. type Source interface { // Get returns the value of the entry with the given key. Get(K) V diff --git a/core/data/endian/BUILD.bazel b/core/data/endian/BUILD.bazel index 798cdaa816..3b6dddd1fe 100644 --- a/core/data/endian/BUILD.bazel +++ b/core/data/endian/BUILD.bazel @@ -33,8 +33,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["endian_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/data/binary:go_default_library", "//core/fault:go_default_library", diff --git a/core/data/endian/doc.go b/core/data/endian/doc.go index 5cf7386cc5..730f6d7033 100644 --- a/core/data/endian/doc.go +++ b/core/data/endian/doc.go @@ -22,5 +22,4 @@ // attempt is made to align them. // // Strings are encoded in C style null terminated form. -// package endian diff --git a/core/data/generic/BUILD.bazel b/core/data/generic/BUILD.bazel index 59a1390d8d..5a3167da3b 100644 --- a/core/data/generic/BUILD.bazel +++ b/core/data/generic/BUILD.bazel @@ -24,8 +24,8 @@ go_library( go_test( name = "go_default_test", srcs = ["generic_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/data/id/BUILD.bazel b/core/data/id/BUILD.bazel index babe5a6275..3fa2ffc77d 100644 --- a/core/data/id/BUILD.bazel +++ b/core/data/id/BUILD.bazel @@ -30,6 +30,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["id_test.go"], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/data/pack/BUILD.bazel b/core/data/pack/BUILD.bazel index 86882f82f3..475bdad35a 100644 --- a/core/data/pack/BUILD.bazel +++ b/core/data/pack/BUILD.bazel @@ -41,8 +41,8 @@ go_library( go_test( name = "go_default_test", srcs = ["pack_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/data/protoutil/testprotos:go_default_library", "//core/log:go_default_library", diff --git a/core/data/pack/README.MD b/core/data/pack/README.md similarity index 100% rename from core/data/pack/README.MD rename to core/data/pack/README.md diff --git a/core/data/pack/dynamic.go b/core/data/pack/dynamic.go index 8da8b973bc..270a81ee41 100644 --- a/core/data/pack/dynamic.go +++ b/core/data/pack/dynamic.go @@ -59,8 +59,14 @@ func (d Dynamic) Format(f fmt.State, r rune) { fields = append(fields, fmt.Sprintf("%v%v", v, suffix)) } } + // TODO: we should sort the fields before formatting, so that we can exit out early, as soon + // as the requested number of chars has been reached. sort.Strings(fields) - fmt.Fprintf(f, "%vᵈ{%s}", d.Desc.GetName(), strings.Join(fields, ", ")) + joined := strings.Join(fields, ", ") + if width, hasWidth := f.Width(); hasWidth && len(joined) > width { + joined = joined[0:width] + "..." + } + fmt.Fprintf(f, "%vᵈ{%s}", d.Desc.GetName(), joined) } } diff --git a/core/data/pod/BUILD.bazel b/core/data/pod/BUILD.bazel index fcad94ca2e..13b817d0b9 100644 --- a/core/data/pod/BUILD.bazel +++ b/core/data/pod/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", diff --git a/core/data/pod/pod.proto b/core/data/pod/pod.proto index 90ea4ca8c0..f507cef53e 100644 --- a/core/data/pod/pod.proto +++ b/core/data/pod/pod.proto @@ -49,6 +49,9 @@ message Value { Sint64Array sint64_array = 26; BoolArray bool_array = 27; StringArray string_array = 28; + + // C++ Types + uint32 char = 29; } } diff --git a/core/data/protoconv/protoconv.go b/core/data/protoconv/protoconv.go index be301c8487..d865eb4f05 100644 --- a/core/data/protoconv/protoconv.go +++ b/core/data/protoconv/protoconv.go @@ -93,9 +93,13 @@ func (e ErrNoConverterRegistered) Error() string { // Register registers the converters toProto and toObject. // toProto must be a function with the signature: -// func(context.Context, O) (P, error) +// +// func(context.Context, O) (P, error) +// // toObject must be a function with the signature: -// func(context.Context, P) (O, error) +// +// func(context.Context, P) (O, error) +// // Where P is the proto message type and O is the object type. func Register(toProto, toObject interface{}) { type ( diff --git a/core/data/protoutil/oneof.go b/core/data/protoutil/oneof.go index 249c6e31b0..0e78fcbb33 100644 --- a/core/data/protoutil/oneof.go +++ b/core/data/protoutil/oneof.go @@ -21,13 +21,13 @@ import ( // OneOf returns the value of a oneof proto field. // For example, given the proto: // -// message M { -// oneof o { -// A a = 1; -// B b = 2; -// C c = 3; -// } -// } +// message M { +// oneof o { +// A a = 1; +// B b = 2; +// C c = 3; +// } +// } // // OneOf(m.o) will return either a, b, c or nil, skipping the wrapper struct. func OneOf(v interface{}) interface{} { diff --git a/core/data/protoutil/testprotos/BUILD.bazel b/core/data/protoutil/testprotos/BUILD.bazel index da97aed901..386161e77d 100644 --- a/core/data/protoutil/testprotos/BUILD.bazel +++ b/core/data/protoutil/testprotos/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", diff --git a/core/data/slice/BUILD.bazel b/core/data/slice/BUILD.bazel index 5655f441bf..46572d152e 100644 --- a/core/data/slice/BUILD.bazel +++ b/core/data/slice/BUILD.bazel @@ -30,8 +30,8 @@ go_test( "slice_test.go", "sort_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/event/as.go b/core/event/as.go index 2a65bb4015..d7757dd51f 100644 --- a/core/event/as.go +++ b/core/event/as.go @@ -23,9 +23,11 @@ import ( // AsHandler wraps a destination into an event handler. // The destination can be one of -// func(context.Context, T, error) error -// chan T -// chan<- T +// +// func(context.Context, T, error) error +// chan T +// chan<- T +// // If it is not, this function will panic, as this is assumed to be a programming error. // If the handler is invoked with an event that is not of type T the handler will return an error. func AsHandler(ctx context.Context, f interface{}) Handler { @@ -52,12 +54,14 @@ func AsHandler(ctx context.Context, f interface{}) Handler { // AsProducer wraps an event generator into an event producer. // The generator can be one of -// func(context.Context) T -// Source -// chan T -// <-chan T -// []T -// [n]T +// +// func(context.Context) T +// Source +// chan T +// <-chan T +// []T +// [n]T +// // If it is not, this function will panic, as this is assumed to be a programming error. func AsProducer(ctx context.Context, f interface{}) Producer { switch src := f.(type) { @@ -94,7 +98,9 @@ func AsProducer(ctx context.Context, f interface{}) Producer { // AsPredicate wraps a function into an event predicate. // The function must be a function of the form -// func(context.Context, T) bool +// +// func(context.Context, T) bool +// // If it is not, this function will panic, as this is assumed to be a programming error. // If the handler is invoked with an event that is not of type T the handler will return an error. func AsPredicate(ctx context.Context, f interface{}) Predicate { diff --git a/core/event/task/BUILD.bazel b/core/event/task/BUILD.bazel index 01768fbed2..f3e9cb4b3f 100644 --- a/core/event/task/BUILD.bazel +++ b/core/event/task/BUILD.bazel @@ -46,8 +46,8 @@ go_test( "signal_test.go", "task_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/event/task/context.go b/core/event/task/context.go index aa1ea01e16..b8460aa9e6 100644 --- a/core/event/task/context.go +++ b/core/event/task/context.go @@ -19,6 +19,17 @@ import ( "time" ) +// Provide a mean to let a context ignore cancellation. Use unexported type and +// variable to force other packages to use IgnoreCancellation(). +type key int + +var ignoreCancellationKey key + +// IgnoreCancellation returns a context that will pretend to never be canceled. +func IgnoreCancellation(ctx context.Context) context.Context { + return context.WithValue(ctx, ignoreCancellationKey, struct{}{}) +} + // CancelFunc is a function type that can be used to stop a context. type CancelFunc context.CancelFunc @@ -46,12 +57,20 @@ func WithTimeout(ctx context.Context, duration time.Duration) (context.Context, // context should be stopped. // See context.Context.Done for more details. func ShouldStop(ctx context.Context) <-chan struct{} { + if ctx.Value(ignoreCancellationKey) != nil { + // return the nil channel from which reading blocks forever + return nil + } return ctx.Done() } // StopReason returns a non-nil error value after Done is closed. // See context.Context.Err for more details. func StopReason(ctx context.Context) error { + if ctx.Value(ignoreCancellationKey) != nil { + // pretend to never be canceled + return nil + } return ctx.Err() } diff --git a/core/fault/BUILD.bazel b/core/fault/BUILD.bazel index a4818fbae4..165287d1b6 100644 --- a/core/fault/BUILD.bazel +++ b/core/fault/BUILD.bazel @@ -29,5 +29,5 @@ go_test( name = "go_default_test", size = "small", srcs = ["fault_test.go"], - embed = [":go_default_library"], + deps = [":go_default_library"], ) diff --git a/core/fault/stacktrace/BUILD.bazel b/core/fault/stacktrace/BUILD.bazel index ffe0df5107..fdee85e3a1 100644 --- a/core/fault/stacktrace/BUILD.bazel +++ b/core/fault/stacktrace/BUILD.bazel @@ -29,8 +29,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["stacktrace_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/fault/stacktrace/stacktrace_test.go b/core/fault/stacktrace/stacktrace_test.go index 8e379ba5b1..fd9a4c02f4 100644 --- a/core/fault/stacktrace/stacktrace_test.go +++ b/core/fault/stacktrace/stacktrace_test.go @@ -31,9 +31,16 @@ import ( // be very careful re-ordering the top of this file, the stack trace captures line numbers //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +//go:noinline func nested3() stacktrace.Callstack { return nested2() } + +//go:noinline func nested2() stacktrace.Callstack { return nested1() } + +//go:noinline func nested1() stacktrace.Callstack { return stacktrace.Capture() } + +//go:noinline func init() { for i := range traces { traces[i].stack = traces[i].fun() @@ -60,30 +67,22 @@ var ( traces = []traceEntry{{ fun: stacktrace.Capture, expect: [][]string{{ - "⇒ core/fault/stacktrace/stacktrace_test.go@39:init.0", + "⇒ core/fault/stacktrace/stacktrace_test.go@46:init.0", }}, }, { fun: nested1, expect: [][]string{{ - "⇒ core/fault/stacktrace/stacktrace_test.go@36:nested1", - "⇒ core/fault/stacktrace/stacktrace_test.go@39:init.0", + "⇒ core/fault/stacktrace/stacktrace_test.go@41:nested1", + "⇒ core/fault/stacktrace/stacktrace_test.go@46:init.0", }}, }, { fun: nested3, expect: [][]string{ { - "⇒ core/fault/stacktrace/stacktrace_test.go@36:nested1", - "⇒ core/fault/stacktrace/stacktrace_test.go@35:nested2", - "⇒ core/fault/stacktrace/stacktrace_test.go@34:nested3", - "⇒ core/fault/stacktrace/stacktrace_test.go@39:init.0", - }, - // Compiling with optimisations can lead to the following - // stack trace: - { - "⇒ core/fault/stacktrace/stacktrace_test.go@36:nested1", - "⇒ core/fault/stacktrace/stacktrace_test.go@34:nested3", - "⇒ core/fault/stacktrace/stacktrace_test.go@34:nested3", - "⇒ core/fault/stacktrace/stacktrace_test.go@39:init.0", + "⇒ core/fault/stacktrace/stacktrace_test.go@41:nested1", + "⇒ core/fault/stacktrace/stacktrace_test.go@38:nested2", + "⇒ core/fault/stacktrace/stacktrace_test.go@35:nested3", + "⇒ core/fault/stacktrace/stacktrace_test.go@46:init.0", }, }, }} diff --git a/core/git/log_test.go b/core/git/log_test.go index f05c9ff524..2a2bfa2480 100644 --- a/core/git/log_test.go +++ b/core/git/log_test.go @@ -26,7 +26,7 @@ func TestParseLog(t *testing.T) { ǁf3cdfb4d9b662e0d66b5779d02370115e136a008ǀBen ClaytonǀUpdate freetype path and fix for breaking changes.ǀ ǁ92ee22e53df7b361224670cf0f5ce4b12cdbc040ǀBen Clayton ǀMerge pull request #154 from google/recreate_ctrlsǀAdd recreateControls flag to the OnDataChanged list/tree event. ǁ73c5a5ba67edfd06d20f9f52249b06984a324d0cǀMr4xǀUpdated Grid Layoutǀ - ǁ1d039c122e95122e68e5862aab3580bba15bd1adǀMr4xǀMerge pull request #1 from google/masterǀUpdate` + ǁ1d039c122e95122e68e5862aab3580bba15bd1adǀMr4xǀMerge pull request #1 from google/fooǀUpdate` expected := []ChangeList{ { SHA: SHA{0xa5, 0xea, 0xeb, 0xbe, 0x90, 0xd6, 0x0b, 0x56, 0x0e, 0x08, 0xa6, 0x75, 0x09, 0x58, 0x75, 0x5a, 0x58, 0xe6, 0xf9, 0x99}, @@ -53,7 +53,7 @@ func TestParseLog(t *testing.T) { { SHA: SHA{0x1d, 0x03, 0x9c, 0x12, 0x2e, 0x95, 0x12, 0x2e, 0x68, 0xe5, 0x86, 0x2a, 0xab, 0x35, 0x80, 0xbb, 0xa1, 0x5b, 0xd1, 0xad}, Author: "Mr4x", - Subject: "Merge pull request #1 from google/master", + Subject: "Merge pull request #1 from google/foo", Description: "Update", }, } diff --git a/core/image/BUILD.bazel b/core/image/BUILD.bazel index d2f629ffde..3ee19e8119 100644 --- a/core/image/BUILD.bazel +++ b/core/image/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", @@ -49,7 +50,6 @@ go_library( "//core/data/id:go_default_library", "//core/data/protoutil:go_default_library", "//core/math/sint:go_default_library", - "//core/math/u64:go_default_library", "//core/os/device:go_default_library", "//core/stream:go_default_library", "//core/stream/fmts:go_default_library", @@ -82,15 +82,17 @@ go_test( name = "go_default_test", size = "small", srcs = [ + "compress_test.go", "decompress_test.go", "image_test.go", "rgba_f32_test.go", ], data = glob(["test_data/*"]), - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/data/endian:go_default_library", "//core/image/astc:go_default_library", + "//core/image/etc:go_default_library", "//core/math/f32:go_default_library", "//core/math/sint:go_default_library", "//core/os/device:go_default_library", diff --git a/core/image/astc.go b/core/image/astc.go index 9c7eaf01ac..e976805cbe 100644 --- a/core/image/astc.go +++ b/core/image/astc.go @@ -15,9 +15,12 @@ package image import ( + "bytes" "fmt" + "github.com/google/gapid/core/data/endian" "github.com/google/gapid/core/math/sint" + "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/stream" ) @@ -42,3 +45,44 @@ func (f *FmtASTC) check(data []byte, w, h, d int) error { func (*FmtASTC) channels() stream.Channels { return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue, stream.Channel_Alpha} } + +// ASTCFrom reads a raw astc image(with header), extracts the header and creates an +// ASTC image format object. +func ASTCFrom(data []byte) (*Data, error) { + r := endian.Reader(bytes.NewBuffer(data), device.LittleEndian) + + if got := r.Uint32(); got != 0x5ca1ab13 { + return nil, fmt.Errorf("Invalid header. Got: %x", got) + } + + blockWidth := uint32(r.Uint8()) + blockHeight := uint32(r.Uint8()) + blockDepth := uint32(r.Uint8()) + + if blockDepth != 1 { + return nil, fmt.Errorf("Got a block depth of %v. Only 2D textures are currently supported", blockDepth) + } + + texelWidth := uint32(r.Uint8()) + 0x100*uint32(r.Uint8()) + 0x10000*uint32(r.Uint8()) + texelHeight := uint32(r.Uint8()) + 0x100*uint32(r.Uint8()) + 0x10000*uint32(r.Uint8()) + texelDepth := uint32(r.Uint8()) + 0x100*uint32(r.Uint8()) + 0x10000*uint32(r.Uint8()) + + blocksX := (texelWidth + blockWidth - 1) / blockWidth + blocksY := (texelHeight + blockHeight - 1) / blockHeight + blocksZ := (texelDepth + blockDepth - 1) / blockDepth + + texelData := make([]byte, blocksX*blocksY*blocksZ*16) + r.Data(texelData) + + if err := r.Error(); err != nil { + return nil, err + } + + return &Data{ + Format: NewASTC("astc", blockWidth, blockHeight, false), + Width: texelWidth, + Height: texelHeight, + Depth: texelDepth, + Bytes: texelData, + }, nil +} diff --git a/core/image/astc/BUILD.bazel b/core/image/astc/BUILD.bazel index 79d73e45b0..a42a1460c1 100644 --- a/core/image/astc/BUILD.bazel +++ b/core/image/astc/BUILD.bazel @@ -17,7 +17,7 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ - "astc.cc", + "astc.cpp", "astc.go", "astc.h", ], diff --git a/core/image/astc/astc.cc b/core/image/astc/astc.cc deleted file mode 100644 index ec6b04b324..0000000000 --- a/core/image/astc/astc.cc +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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 "astc.h" - -#include "third_party/astc-encoder/Source/astc_codec_internals.h" - -// astc-encoder global variables... *sigh* -int alpha_force_use_of_hdr = 0; -int perform_srgb_transform = 0; -int rgb_force_use_of_hdr = 0; -int print_diagnostics = 0; - -// Functions that are used in compilation units we depend on, but don't actually -// use. -int astc_codec_unlink(const char *filename) { return 0; } -void astc_codec_internal_error(const char *filename, int linenum) {} -astc_codec_image *load_ktx_uncompressed_image(const char *filename, int padding, int *result) { return 0; } -astc_codec_image *load_dds_uncompressed_image(const char *filename, int padding, int *result) { return 0; } -astc_codec_image *load_tga_image(const char *tga_filename, int padding, int *result) { return 0; } -astc_codec_image *load_image_with_stb(const char *filename, int padding, int *result) { return 0; } -int store_ktx_uncompressed_image(const astc_codec_image * img, const char *filename, int bitness) { return 0; } -int store_dds_uncompressed_image(const astc_codec_image * img, const char *filename, int bitness) { return 0; } -int store_tga_image(const astc_codec_image * img, const char *tga_filename, int bitness) { return 0; } - -uint8_t float2byte(float f) { - if (f > 1.0f) { return 255; } - if (f < 0.0f) { return 0; } - return (uint8_t)(f * 255.0f + 0.5f); -} - -extern "C" void init_astc() { - build_quantization_mode_table(); -} - -extern "C" void decompress_astc( - uint8_t* in, - uint8_t* out, - uint32_t width, - uint32_t height, - uint32_t block_width, - uint32_t block_height) { - - uint32_t blocks_x = (width + block_width - 1) / block_width; - uint32_t blocks_y = (height + block_height - 1) / block_height; - - imageblock pb; - for (uint32_t by = 0; by < blocks_y; by++) { - for (uint32_t bx = 0; bx < blocks_x; bx++) { - physical_compressed_block pcb = *(physical_compressed_block*) in; - symbolic_compressed_block scb; - physical_to_symbolic(block_width, block_height, 1, pcb, &scb); - decompress_symbolic_block(DECODE_LDR, block_width, block_height, 1, 0, 0, 0, &scb, &pb); - in += 16; - - const float* data = pb.orig_data; - for (uint32_t dy = 0; dy < block_height; dy++) { - uint32_t y = by*block_height + dy; - for (uint32_t dx = 0; dx < block_width; dx++) { - uint32_t x = bx*block_width + dx; - if (x < width && y < height) { - uint8_t* pxl = &out[(width*y+x)*4]; - pxl[0] = float2byte(data[0]); - pxl[1] = float2byte(data[1]); - pxl[2] = float2byte(data[2]); - pxl[3] = float2byte(data[3]); - } - data += 4; - } - } - } - } -} \ No newline at end of file diff --git a/core/image/astc/astc.cpp b/core/image/astc/astc.cpp new file mode 100644 index 0000000000..90474026ef --- /dev/null +++ b/core/image/astc/astc.cpp @@ -0,0 +1,92 @@ +// Copyright (C) 2017 Google Inc. +// +// 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 "astc.h" +#include "third_party/astc-encoder/Source/astcenc.h" + +static_assert(sizeof(astc_error) >= sizeof(astcenc_error), + "astc_error should superset of astcenc_error"); + +extern "C" astc_error compress_astc(uint8_t* input_image_raw, + uint8_t* output_image_raw, uint32_t width, + uint32_t height, uint32_t block_width, + uint32_t block_height, uint32_t is_srgb) { + astcenc_profile profile = is_srgb ? ASTCENC_PRF_LDR_SRGB : ASTCENC_PRF_LDR; + astcenc_config config{}; + astcenc_error result = astcenc_config_init( + profile, block_width, block_height, 1, ASTCENC_PRE_FASTEST, 0, &config); + if (result != ASTCENC_SUCCESS) { + return result; + } + + astcenc_context* codec_context; + result = astcenc_context_alloc(&config, 1, &codec_context); + if (result != ASTCENC_SUCCESS) { + return result; + } + + astcenc_image uncompressed_image{width, height, 1, ASTCENC_TYPE_U8, + reinterpret_cast(&input_image_raw)}; + astcenc_swizzle swz_encode{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, + ASTCENC_SWZ_A}; + result = + astcenc_compress_image(codec_context, &uncompressed_image, &swz_encode, + output_image_raw, 4 * width * height, 0); + astcenc_context_free(codec_context); + return result; +} + +extern "C" astc_error decompress_astc(uint8_t* input_image_raw, + uint8_t* output_image_raw, uint32_t width, + uint32_t height, uint32_t block_width, + uint32_t block_height) { + astcenc_config config{}; + astcenc_error result = + astcenc_config_init(ASTCENC_PRF_LDR, block_width, block_height, 1, + ASTCENC_PRE_FASTEST, 0, &config); + if (result != ASTCENC_SUCCESS) { + return result; + } + + astcenc_context* codec_context; + result = astcenc_context_alloc(&config, 1, &codec_context); + if (result != ASTCENC_SUCCESS) { + return result; + } + + astcenc_image output_image{width, height, 1, ASTCENC_TYPE_U8, + reinterpret_cast(&output_image_raw)}; + astcenc_swizzle swz_decode{ASTCENC_SWZ_R, ASTCENC_SWZ_G, ASTCENC_SWZ_B, + ASTCENC_SWZ_A}; + result = astcenc_decompress_image(codec_context, input_image_raw, + 4 * width * height, &output_image, + &swz_decode, 0); + astcenc_context_free(codec_context); + return ASTCENC_SUCCESS; +} + +extern "C" const char* get_astc_error_string(astc_error error_code) { + switch (error_code) { + case ASTCENC_ERR_BAD_BLOCK_SIZE: + return "ERROR: Block size is invalid"; + case ASTCENC_ERR_BAD_CPU_ISA: + return "ERROR: Required SIMD ISA support missing on this CPU"; + case ASTCENC_ERR_BAD_CPU_FLOAT: + return "ERROR: astcenc must not be compiled with -ffast-math"; + default: + return astcenc_get_error_string(static_cast(error_code)); + } +} diff --git a/core/image/astc/astc.go b/core/image/astc/astc.go index e474a53545..ba6f0e0d9d 100644 --- a/core/image/astc/astc.go +++ b/core/image/astc/astc.go @@ -12,16 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package astc implements ASTC texture decompression. +// Package astc implements ASTC texture compression and decompression. // // astc is in a separate package from image as it contains cgo code that can // slow builds. package astc -// #include "astc.h" +/* +#include "astc.h" +*/ import "C" import ( + "fmt" "unsafe" "github.com/google/gapid/core/image" @@ -87,61 +90,122 @@ func NewSRGB8_ALPHA8_10x10(name string) *image.Format { return image.NewASTC(nam func NewSRGB8_ALPHA8_12x10(name string) *image.Format { return image.NewASTC(name, 12, 10, true) } func NewSRGB8_ALPHA8_12x12(name string) *image.Format { return image.NewASTC(name, 12, 12, true) } +type converterLayout struct { + uncompressed *image.Format + compressed *image.Format +} + func init() { - C.init_astc() - - for _, f := range []struct { - src *image.Format - dst *image.Format - }{ - {RGBA_4x4, image.RGBA_U8_NORM}, - {RGBA_5x4, image.RGBA_U8_NORM}, - {RGBA_5x5, image.RGBA_U8_NORM}, - {RGBA_6x5, image.RGBA_U8_NORM}, - {RGBA_6x6, image.RGBA_U8_NORM}, - {RGBA_8x5, image.RGBA_U8_NORM}, - {RGBA_8x6, image.RGBA_U8_NORM}, - {RGBA_8x8, image.RGBA_U8_NORM}, - {RGBA_10x5, image.RGBA_U8_NORM}, - {RGBA_10x6, image.RGBA_U8_NORM}, - {RGBA_10x8, image.RGBA_U8_NORM}, - {RGBA_10x10, image.RGBA_U8_NORM}, - {RGBA_12x10, image.RGBA_U8_NORM}, - {RGBA_12x12, image.RGBA_U8_NORM}, - {SRGB8_ALPHA8_4x4, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_5x4, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_5x5, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_6x5, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_6x6, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_8x5, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_8x6, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_8x8, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_10x5, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_10x6, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_10x8, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_10x10, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_12x10, image.SRGBA_U8_NORM}, - {SRGB8_ALPHA8_12x12, image.SRGBA_U8_NORM}, - } { - f := f - image.RegisterConverter(f.src, f.dst, func(src []byte, w, h, d int) ([]byte, error) { - dst := make([]byte, w*h*d*4) - sliceSize := f.src.Size(w, h, 1) - for z := 0; z < d; z++ { - dst, src := dst[z*w*h*4:], src[z*sliceSize:] - in := (unsafe.Pointer)(&src[0]) - out := (unsafe.Pointer)(&dst[0]) - blockW := f.src.GetAstc().BlockWidth - blockH := f.src.GetAstc().BlockHeight - C.decompress_astc( - (*C.uint8_t)(in), - (*C.uint8_t)(out), - (C.uint32_t)(w), - (C.uint32_t)(h), - (C.uint32_t)(blockW), - (C.uint32_t)(blockH)) - } - return dst, nil + compressionSupportMap := []converterLayout{ + {image.RGBA_U8_NORM, RGBA_4x4}, + {image.RGBA_U8_NORM, RGBA_5x4}, + {image.RGBA_U8_NORM, RGBA_5x5}, + {image.RGBA_U8_NORM, RGBA_6x5}, + {image.RGBA_U8_NORM, RGBA_6x6}, + {image.RGBA_U8_NORM, RGBA_8x5}, + {image.RGBA_U8_NORM, RGBA_8x6}, + {image.RGBA_U8_NORM, RGBA_8x8}, + {image.RGBA_U8_NORM, RGBA_10x5}, + {image.RGBA_U8_NORM, RGBA_10x6}, + {image.RGBA_U8_NORM, RGBA_10x8}, + {image.RGBA_U8_NORM, RGBA_10x10}, + {image.RGBA_U8_NORM, RGBA_12x10}, + {image.RGBA_U8_NORM, RGBA_12x12}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_4x4}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_5x4}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_5x5}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_6x5}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_6x6}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_8x5}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_8x6}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_8x8}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_10x5}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_10x6}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_10x8}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_10x10}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_12x10}, + {image.SRGBA_U8_NORM, SRGB8_ALPHA8_12x12}, + } + + for _, conversion := range compressionSupportMap { + // Intentional local copy + conv := conversion + image.RegisterConverter(conv.uncompressed, conv.compressed, func(src []byte, width, height, depth int) ([]byte, error) { + return compress(src, width, height, depth, conv.compressed) + }) + + image.RegisterConverter(conv.compressed, conv.uncompressed, func(src []byte, width, height, depth int) ([]byte, error) { + return decompress(src, width, height, depth, conv.compressed) }) } } + +func decompress(src []byte, width int, height int, depth int, format *image.Format) ([]byte, error) { + dst := make([]byte, width*height*depth*4) + srcSliceSize := format.Size(width, height, 1) + dstSliceSize := width * height * 4 + + astcFormat := format.GetAstc() + blockWidth := astcFormat.GetBlockWidth() + blockHeight := astcFormat.GetBlockHeight() + + for z := 0; z < depth; z++ { + currentSrcSlice := src[z*srcSliceSize:] + currentDstSlice := dst[z*dstSliceSize:] + inputImageData := (unsafe.Pointer)(¤tSrcSlice[0]) + outputImageData := (unsafe.Pointer)(¤tDstSlice[0]) + + result := C.decompress_astc( + (*C.uint8_t)(inputImageData), + (*C.uint8_t)(outputImageData), + (C.uint32_t)(width), + (C.uint32_t)(height), + (C.uint32_t)(blockWidth), + (C.uint32_t)(blockHeight), + ) + + if result != 0 { + return nil, fmt.Errorf("ASTC decompression failed : %s", + C.GoString(C.get_astc_error_string(result))) + } + } + return dst, nil +} + +func compress(src []byte, width int, height int, depth int, format *image.Format) ([]byte, error) { + astcFormat := format.GetAstc() + blockWidth := astcFormat.GetBlockWidth() + blockHeight := astcFormat.GetBlockHeight() + isSrgb := 0 + if astcFormat.GetSrgb() { + isSrgb = 1 + } + + srcSliceSize := width * height * 4 + dstSliceSize := format.Size(width, height, 1) + dst := make([]byte, dstSliceSize*depth) + + for z := 0; z < depth; z++ { + currentSrcSlice := src[srcSliceSize*z:] + currentDstSlice := dst[dstSliceSize*z:] + inputImageData := (unsafe.Pointer)(¤tSrcSlice[0]) + outputImageData := (unsafe.Pointer)(¤tDstSlice[0]) + + result := C.compress_astc( + (*C.uint8_t)(inputImageData), + (*C.uint8_t)(outputImageData), + (C.uint32_t)(width), + (C.uint32_t)(height), + (C.uint32_t)(blockWidth), + (C.uint32_t)(blockHeight), + (C.uint32_t)(isSrgb), + ) + + if result != 0 { + return nil, fmt.Errorf("ASTC compression failed: %s", + C.GoString(C.get_astc_error_string(result))) + } + } + + return dst, nil +} diff --git a/core/image/astc/astc.h b/core/image/astc/astc.h index 8867140270..5d7b0ca9d7 100644 --- a/core/image/astc/astc.h +++ b/core/image/astc/astc.h @@ -12,17 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. +#ifndef ASTC_H_ +#define ASTC_H_ + #include #ifdef __cplusplus extern "C" { #endif -void init_astc(); +typedef int astc_error; + +astc_error compress_astc(uint8_t* in, uint8_t* out, uint32_t width, + uint32_t height, uint32_t block_width, + uint32_t block_height, uint32_t is_srgb); + +astc_error decompress_astc(uint8_t* in, uint8_t* out, uint32_t width, + uint32_t height, uint32_t block_width, + uint32_t block_height); -void decompress_astc(uint8_t* in, uint8_t* out, uint32_t width, uint32_t height, - uint32_t block_width, uint32_t block_height); +const char* get_astc_error_string(astc_error error_code); #ifdef __cplusplus } // extern "C" #endif + +#endif diff --git a/core/image/compress_test.go b/core/image/compress_test.go new file mode 100644 index 0000000000..31d6265960 --- /dev/null +++ b/core/image/compress_test.go @@ -0,0 +1,175 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package image_test + +import ( + "fmt" + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/google/gapid/core/image" + "github.com/google/gapid/core/image/astc" + "github.com/google/gapid/core/image/etc" +) + +type testImageInfo struct { + compressed *image.Format + ext string + uncompressed *image.Format +} + +func TestCompressors(t *testing.T) { + // TODO(melihyalcin): We don't have a converter for'png' to 'RG_S16_NORM' or 'R_S16_NORM' + // If we need this conversion, we can add the converter then we can read + // the png and test it + + // TODO(melihyalcin): Conversion quality for ETC2_R_U11_NORM is low (~0.20) we don't need + // to test that quality for our use case. If we need, we have to look at how to improve it. + testImageInfos := []testImageInfo{ + {astc.RGBA_4x4, ".astc", image.RGBA_U8_NORM}, + {etc.ETC2_RGBA_U8U8U8U1_NORM, ".ktx", image.RGBA_U8_NORM}, + {etc.ETC2_RGBA_U8_NORM, ".ktx", image.RGBA_U8_NORM}, + {etc.ETC2_RGB_U8_NORM, ".ktx", image.RGB_U8_NORM}, + // {etc.ETC2_RG_S11_NORM, ".ktx", image.RG_S16_NORM}, + // {etc.ETC2_RG_U11_NORM, ".ktx", image.RG_U16_NORM}, + // {etc.ETC2_R_S11_NORM, ".ktx", image.R_S16_NORM}, + // {etc.ETC2_R_U11_NORM, ".ktx", image.R_U16_NORM}, + } + + for _, testImage := range testImageInfos { + uncompressedImg, err := getUncompressedImage(testImage) + if err != nil { + t.Error(err) + continue + } + + compressedImage, err := uncompressedImg.Convert(testImage.compressed) + if err != nil { + t.Errorf("Failed to convert '%s' from %v to %v: %v", testImage.compressed.Name, uncompressedImg.Format.Name, testImage.compressed.Name, err) + continue + } + + refImg, err := getRefImage(testImage) + if err != nil { + t.Error(err) + continue + } + + errs := outputDifference(testImage.compressed.Name, refImg, compressedImage) + if errs != nil && len(errs) > 0 { + for _, err := range errs { + t.Error(err) + } + continue + } + } +} + +func getRefImage(testImage testImageInfo) (*image.Data, error) { + imagePath := filepath.Join("test_data", testImage.compressed.Name+testImage.ext) + imageData, err := ioutil.ReadFile(imagePath) + if err != nil { + return nil, fmt.Errorf("Failed to read '%s': %v", imagePath, err) + } + + switch testImage.ext { + case ".astc": + imageASTC, err := image.ASTCFrom(imageData) + if err != nil { + return nil, fmt.Errorf("Failed to read '%s': %v", testImage.compressed.Name, err) + } + + if imageASTC.Format.Key() != testImage.compressed.Key() { + return nil, fmt.Errorf("%v was not the expected format. Expected %v, got %v", + imagePath, testImage.compressed.Name, imageASTC.Format.Name) + } + + return imageASTC, nil + case ".ktx": + imageKTX, err := loadKTX(imageData) + if err != nil { + return nil, fmt.Errorf("Failed to read '%s': %v", imagePath, err) + } + + if imageKTX.Format.Key() != testImage.compressed.Key() { + return nil, fmt.Errorf("%v was not the expected format. Expected %v, got %v", + imagePath, testImage.compressed.Name, imageKTX.Format.Name) + } + + return imageKTX, nil + default: + return nil, fmt.Errorf("This should not happen: Unknown Format") + } +} + +func getUncompressedImage(testImage testImageInfo) (*image.Data, error) { + imagePath := filepath.Join("test_data", testImage.compressed.Name+".png") + + imagePNGData, err := ioutil.ReadFile(imagePath) + if err != nil { + return nil, fmt.Errorf("Failed to read '%s': %v", imagePath, err) + } + imagePNG, err := image.PNGFrom(imagePNGData) + if err != nil { + return nil, fmt.Errorf("Failed to read PNG '%s': %v", imagePath, err) + } + + img, err := imagePNG.Convert(testImage.uncompressed) + if err != nil { + return nil, fmt.Errorf("Failed to convert '%s' from PNG to %v: %v", imagePath, testImage.uncompressed.Name, err) + } + + return img, nil +} + +func outputDifference(name string, refImage *image.Data, finalImage *image.Data) []error { + diff, err := image.Difference(finalImage, refImage) + if err != nil { + return []error{fmt.Errorf("Difference returned error: %v", err)} + } + + outputPath := name + "-output.png" + errorPath := name + "-error.png" + + errorMargin := float32(0.01) + if diff <= errorMargin { + os.Remove(outputPath) + os.Remove(errorPath) + return nil + } + + errs := make([]error, 0, 0) + errs = append(errs, fmt.Errorf("%v produced unexpected difference due to compression (%v)", name, diff)) + if outPNG, err := finalImage.Convert(image.PNG); err == nil { + ioutil.WriteFile(outputPath, outPNG.Bytes, 0666) + } else { + errs = append(errs, fmt.Errorf("Could not write output file: %v", err)) + } + for i := range finalImage.Bytes { + g, e := int(finalImage.Bytes[i]), int(refImage.Bytes[i]) + if g != e { + finalImage.Bytes[i] = 255 // Highlight errors + } + } + if outPNG, err := finalImage.Convert(image.PNG); err == nil { + ioutil.WriteFile(errorPath, outPNG.Bytes, 0666) + } else { + errs = append(errs, fmt.Errorf("Could not write error file: %v", err)) + } + + return errs +} diff --git a/core/image/convert.go b/core/image/convert.go index 1eb5e4d6eb..2e81480cc5 100644 --- a/core/image/convert.go +++ b/core/image/convert.go @@ -37,12 +37,12 @@ var registeredConverters = make(map[srcDstFmt]Converter) func RegisterConverter(src, dst *Format, c Converter) { key := srcDstFmt{src.Key(), dst.Key()} if _, found := registeredConverters[key]; found { - panic(fmt.Errorf("Converter from %s to %s already registered", src, dst)) + panic(fmt.Errorf("Converter from %s to %s already registered", src.Name, dst.Name)) } registeredConverters[key] = c } -func registered(src, dst *Format) bool { +func Registered(src, dst *Format) bool { key := srcDstFmt{src.Key(), dst.Key()} _, found := registeredConverters[key] return found @@ -82,7 +82,7 @@ func Convert(data []byte, width, height, depth int, srcFmt, dstFmt *Format) ([]b } return nil, fmt.Errorf("No converter registered that can convert from format '%s' to '%s'", - srcFmt, dstFmt) + srcFmt.Name, dstFmt.Name) } func convertDirect(data []byte, width, height, depth int, srcFmt, dstFmt *Format) ([]byte, error) { @@ -92,7 +92,7 @@ func convertDirect(data []byte, width, height, depth int, srcFmt, dstFmt *Format } if err := srcFmt.Check(data, width, height, depth); err != nil { - return nil, fmt.Errorf("Source data of format %s is invalid: %s", srcFmt, err) + return nil, fmt.Errorf("Source data of format %s is invalid: %s", srcFmt.Name, err) } // Look for a registered converter. diff --git a/core/image/decompress_test.go b/core/image/decompress_test.go index 3e58b397ac..528a08f1cd 100644 --- a/core/image/decompress_test.go +++ b/core/image/decompress_test.go @@ -25,6 +25,7 @@ import ( "github.com/google/gapid/core/data/endian" "github.com/google/gapid/core/image" "github.com/google/gapid/core/image/astc" + "github.com/google/gapid/core/image/etc" "github.com/google/gapid/core/math/sint" "github.com/google/gapid/core/os/device" ) @@ -79,16 +80,16 @@ func loadKTX(data []byte) (*image.Data, error) { } formats := map[uint32]*image.Format{ - 0x9270: image.ETC2_R_U11_NORM, // GL_COMPRESSED_R11_EAC - 0x9271: image.ETC2_R_S11_NORM, // GL_COMPRESSED_SIGNED_R11_EAC - 0x9272: image.ETC2_RG_U11_NORM, // GL_COMPRESSED_RG11_EAC - 0x9273: image.ETC2_RG_S11_NORM, // GL_COMPRESSED_SIGNED_RG11_EAC - 0x9274: image.ETC2_RGB_U8_NORM, // GL_COMPRESSED_RGB8_ETC2 - 0x9275: image.ETC2_SRGB_U8_NORM, // GL_COMPRESSED_SRGB8_ETC2 - 0x9276: image.ETC2_RGBA_U8U8U8U1_NORM, // GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 - 0x9277: image.ETC2_SRGBA_U8U8U8U1_NORM, // GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 - 0x9278: image.ETC2_RGBA_U8_NORM, // GL_COMPRESSED_RGBA8_ETC2_EAC - 0x9279: image.ETC2_SRGBA_U8_NORM, // GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC + 0x9270: etc.ETC2_R_U11_NORM, // GL_COMPRESSED_R11_EAC + 0x9271: etc.ETC2_R_S11_NORM, // GL_COMPRESSED_SIGNED_R11_EAC + 0x9272: etc.ETC2_RG_U11_NORM, // GL_COMPRESSED_RG11_EAC + 0x9273: etc.ETC2_RG_S11_NORM, // GL_COMPRESSED_SIGNED_RG11_EAC + 0x9274: etc.ETC2_RGB_U8_NORM, // GL_COMPRESSED_RGB8_ETC2 + 0x9275: etc.ETC2_SRGB_U8_NORM, // GL_COMPRESSED_SRGB8_ETC2 + 0x9276: etc.ETC2_RGBA_U8U8U8U1_NORM, // GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 + 0x9277: etc.ETC2_SRGBA_U8U8U8U1_NORM, // GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 + 0x9278: etc.ETC2_RGBA_U8_NORM, // GL_COMPRESSED_RGBA8_ETC2_EAC + 0x9279: etc.ETC2_SRGBA_U8_NORM, // GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC } format, ok := formats[glInternalFormat] if !ok { @@ -118,45 +119,6 @@ glBaseInternalFormat=0x%x }, nil } -func loadASTC(data []byte) (*image.Data, error) { - r := endian.Reader(bytes.NewBuffer(data), device.LittleEndian) - - if got := r.Uint32(); got != 0x5ca1ab13 { - return nil, fmt.Errorf("Invalid header. Got: %x", got) - } - - blockWidth := uint32(r.Uint8()) - blockHeight := uint32(r.Uint8()) - blockDepth := uint32(r.Uint8()) - - if blockDepth != 1 { - return nil, fmt.Errorf("Got a block depth of %v. Only 2D textures are currently supported", blockDepth) - } - - texelWidth := uint32(r.Uint8()) + 0x100*uint32(r.Uint8()) + 0x10000*uint32(r.Uint8()) - texelHeight := uint32(r.Uint8()) + 0x100*uint32(r.Uint8()) + 0x10000*uint32(r.Uint8()) - texelDepth := uint32(r.Uint8()) + 0x100*uint32(r.Uint8()) + 0x10000*uint32(r.Uint8()) - - blocksX := (texelWidth + blockWidth - 1) / blockWidth - blocksY := (texelHeight + blockHeight - 1) / blockHeight - blocksZ := (texelDepth + blockDepth - 1) / blockDepth - - texelData := make([]byte, blocksX*blocksY*blocksZ*16) - r.Data(texelData) - - if err := r.Error(); err != nil { - return nil, err - } - - return &image.Data{ - Format: image.NewASTC("astc", blockWidth, blockHeight, false), - Width: texelWidth, - Height: texelHeight, - Depth: texelDepth, - Bytes: texelData, - }, nil -} - func TestDecompressors(t *testing.T) { // For these tests we need to check that the S16_NORM formats match the // U8_NORM PNGs. There's no generic way to do this, so we declare our @@ -168,13 +130,13 @@ func TestDecompressors(t *testing.T) { fmt *image.Format ext string }{ - {image.ETC2_RGBA_U8U8U8U1_NORM, ".ktx"}, - {image.ETC2_RGBA_U8_NORM, ".ktx"}, - {image.ETC2_RGB_U8_NORM, ".ktx"}, - {image.ETC2_RG_S11_NORM, ".ktx"}, - {image.ETC2_RG_U11_NORM, ".ktx"}, - {image.ETC2_R_S11_NORM, ".ktx"}, - {image.ETC2_R_U11_NORM, ".ktx"}, + {etc.ETC2_RGBA_U8U8U8U1_NORM, ".ktx"}, + {etc.ETC2_RGBA_U8_NORM, ".ktx"}, + {etc.ETC2_RGB_U8_NORM, ".ktx"}, + {etc.ETC2_RG_S11_NORM, ".ktx"}, + {etc.ETC2_RG_U11_NORM, ".ktx"}, + {etc.ETC2_R_S11_NORM, ".ktx"}, + {etc.ETC2_R_U11_NORM, ".ktx"}, {image.S3_DXT1_RGB, ".bin"}, {image.S3_DXT1_RGBA, ".bin"}, {image.S3_DXT3_RGBA, ".bin"}, @@ -223,13 +185,13 @@ func TestDecompressors(t *testing.T) { if ktx.Format.Key() != test.fmt.Key() { t.Errorf("%v was not the expected format. Expected %v, got %v", - inPath, test.fmt, ktx.Format) + inPath, test.fmt.Name, ktx.Format.Name) continue } in = ktx case ".astc": - astc, err := loadASTC(data) + astc, err := image.ASTCFrom(data) if err != nil { t.Errorf("Failed to read '%s': %v", inPath, err) continue @@ -237,7 +199,7 @@ func TestDecompressors(t *testing.T) { if astc.Format.Key() != test.fmt.Key() { t.Errorf("%v was not the expected format. Expected %v, got %v", - inPath, test.fmt, astc.Format) + inPath, test.fmt.Name, astc.Format.Name) continue } in = astc @@ -257,7 +219,7 @@ func TestDecompressors(t *testing.T) { out, err := in.Convert(image.RGBA_U8_NORM) if err != nil { - t.Errorf("Failed to convert '%s' from %v to %v: %v", inPath, test.fmt, image.RGBA_U8_NORM, err) + t.Errorf("Failed to convert '%s' from %v to %v: %v", inPath, test.fmt.Name, image.RGBA_U8_NORM.Name, err) continue } diff --git a/core/image/etc/BUILD.bazel b/core/image/etc/BUILD.bazel new file mode 100644 index 0000000000..b4dc0c77e6 --- /dev/null +++ b/core/image/etc/BUILD.bazel @@ -0,0 +1,37 @@ +# Copyright (C) 2021 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = [ + "etc.go", + "etc2.h", + "etc2_compressor.cpp", + "etc2_decompressor.go", + ], + cdeps = ["@etc2comp//:etc2comp"], + cgo = True, + clinkopts = [], # keep + importpath = "github.com/google/gapid/core/image/etc", + visibility = ["//visibility:public"], + deps = [ + "//core/data/endian:go_default_library", + "//core/image:go_default_library", + "//core/math/sint:go_default_library", + "//core/math/u64:go_default_library", + "//core/os/device:go_default_library", + ], +) diff --git a/core/image/etc/etc.go b/core/image/etc/etc.go new file mode 100644 index 0000000000..30e983760a --- /dev/null +++ b/core/image/etc/etc.go @@ -0,0 +1,308 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +// Package etc implements etc texture compression and decompression. +// +// etc is in a separate package from image as it contains cgo code that can +// slow builds. +package etc + +/* +#include "etc2.h" +#include "stdlib.h" +*/ +import "C" + +import ( + "fmt" + "unsafe" + + "github.com/google/gapid/core/image" +) + +var ( + // ETC2 + ETC2_RGB_U8_NORM = NewETC2_RGB_U8_NORM("ETC2_RGB_U8_NORM") + ETC2_RGBA_U8_NORM = NewETC2_RGBA_U8_NORM("ETC2_RGBA_U8_NORM") + ETC2_RGBA_U8U8U8U1_NORM = NewETC2_RGBA_U8U8U8U1_NORM("ETC2_RGBA_U8U8U8U1_NORM") + ETC2_SRGB_U8_NORM = NewETC2_SRGB_U8_NORM("ETC2_SRGB_U8_NORM") + ETC2_SRGBA_U8_NORM = NewETC2_SRGBA_U8_NORM("ETC2_SRGBA_U8_NORM") + ETC2_SRGBA_U8U8U8U1_NORM = NewETC2_SRGBA_U8U8U8U1_NORM("ETC2_SRGBA_U8U8U8U1_NORM") + + // EAC + ETC2_R_U11_NORM = NewETC2_R_U11_NORM("ETC2_R_U11_NORM") + ETC2_RG_U11_NORM = NewETC2_RG_U11_NORM("ETC2_RG_U11_NORM") + ETC2_R_S11_NORM = NewETC2_R_S11_NORM("ETC2_R_S11_NORM") + ETC2_RG_S11_NORM = NewETC2_RG_S11_NORM("ETC2_RG_S11_NORM") + + // ETC 1 + ETC1_RGB_U8_NORM = NewETC1_RGB_U8_NORM("ETC1_RGB_U8_NORM") + + formatToCEnum = map[interface{}]C.enum_etc_format{ + ETC2_RGB_U8_NORM: C.ETC2_RGB_U8_NORM, + ETC2_RGBA_U8_NORM: C.ETC2_RGBA_U8_NORM, + ETC2_RGBA_U8U8U8U1_NORM: C.ETC2_RGBA_U8U8U8U1_NORM, + ETC2_SRGB_U8_NORM: C.ETC2_SRGB_U8_NORM, + ETC2_SRGBA_U8_NORM: C.ETC2_SRGBA_U8_NORM, + ETC2_SRGBA_U8U8U8U1_NORM: C.ETC2_SRGBA_U8U8U8U1_NORM, + ETC2_R_U11_NORM: C.ETC2_R_U11_NORM, + ETC2_RG_U11_NORM: C.ETC2_RG_U11_NORM, + ETC2_R_S11_NORM: C.ETC2_R_S11_NORM, + ETC2_RG_S11_NORM: C.ETC2_RG_S11_NORM, + ETC1_RGB_U8_NORM: C.ETC1_RGB_U8_NORM, + } +) + +func NewETC2_RGB_U8_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_RGB, image.FmtETC2_ALPHA_NONE) +} +func NewETC2_RGBA_U8_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_RGB, image.FmtETC2_ALPHA_8BIT) +} +func NewETC2_RGBA_U8U8U8U1_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_RGB, image.FmtETC2_ALPHA_1BIT) +} +func NewETC2_SRGB_U8_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_SRGB, image.FmtETC2_ALPHA_NONE) +} +func NewETC2_SRGBA_U8_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_SRGB, image.FmtETC2_ALPHA_8BIT) +} +func NewETC2_SRGBA_U8U8U8U1_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_SRGB, image.FmtETC2_ALPHA_1BIT) +} +func NewETC2_R_U11_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_R, image.FmtETC2_ALPHA_NONE) +} +func NewETC2_RG_U11_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_RG, image.FmtETC2_ALPHA_NONE) +} +func NewETC2_R_S11_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_R_SIGNED, image.FmtETC2_ALPHA_NONE) +} +func NewETC2_RG_S11_NORM(name string) *image.Format { + return image.NewETC2(name, image.FmtETC2_RG_SIGNED, image.FmtETC2_ALPHA_NONE) +} +func NewETC1_RGB_U8_NORM(name string) *image.Format { + return image.NewETC1_RGB_U8_NORM(name) +} + +type converterLayout struct { + uncompressed *image.Format + compressed *image.Format +} + +type etcLayout struct { + converterLayout +} + +type eacLayout struct { + converterLayout + channels int +} + +func init() { + //ETC2 Formats + etc2SupportMap := []etcLayout{ + {converterLayout{image.RGBA_U8_NORM, ETC2_RGB_U8_NORM}}, + {converterLayout{image.RGBA_U8_NORM, ETC2_RGBA_U8_NORM}}, + {converterLayout{image.RGBA_U8_NORM, ETC2_RGBA_U8U8U8U1_NORM}}, + {converterLayout{image.SRGBA_U8_NORM, ETC2_SRGB_U8_NORM}}, + {converterLayout{image.SRGBA_U8_NORM, ETC2_SRGBA_U8_NORM}}, + {converterLayout{image.SRGBA_U8_NORM, ETC2_SRGBA_U8U8U8U1_NORM}}, + } + + for _, conversion := range etc2SupportMap { + // Intentional local copy + conv := conversion + image.RegisterConverter(conv.compressed, conv.uncompressed, func(src []byte, w, h, d int) ([]byte, error) { + compressedFormat, ok := conv.compressed.Format.(*image.Format_Etc2) + if !ok { + panic("This should always be an ETC2 format") + } + return decodeETC(src, w, h, d, compressedFormat.Etc2.GetAlphaMode()) + }) + + image.RegisterConverter(conv.uncompressed, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + bytesPerPixel := 4 + return compress(src, w, h, d, conv.compressed, bytesPerPixel) + }) + } + + // EAC formats + etcU11SupportMap := []eacLayout{ + {converterLayout{image.R_U16_NORM, ETC2_R_U11_NORM}, 1}, + {converterLayout{image.RG_U16_NORM, ETC2_RG_U11_NORM}, 2}, + } + + for _, conversion := range etcU11SupportMap { + // Intentional local copy + conv := conversion + image.RegisterConverter(conv.compressed, conv.uncompressed, func(src []byte, w, h, d int) ([]byte, error) { + return decodeETCU11(src, w, h, d, conv.channels) + }) + + image.RegisterConverter(conv.uncompressed, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + bytesPerPixel := conv.channels * 2 + return compress(src, w, h, d, conv.compressed, bytesPerPixel) + }) + } + + etcS11SupportMap := []eacLayout{ + {converterLayout{image.R_S16_NORM, ETC2_R_S11_NORM}, 1}, + {converterLayout{image.RG_S16_NORM, ETC2_RG_S11_NORM}, 2}, + } + + for _, conversion := range etcS11SupportMap { + // Intentional local copy + conv := conversion + image.RegisterConverter(conv.compressed, conv.uncompressed, func(src []byte, w, h, d int) ([]byte, error) { + return decodeETCS11(src, w, h, d, conv.channels) + }) + + image.RegisterConverter(conv.uncompressed, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + bytesPerPixel := conv.channels * 2 + return compress(src, w, h, d, conv.compressed, bytesPerPixel) + }) + } + + // ETC1 formats + etc1SupportMap := []converterLayout{ + {image.RGB_U8_NORM, ETC1_RGB_U8_NORM}, + {image.RGBA_U8_NORM, ETC1_RGB_U8_NORM}, + } + + for _, conversion := range etc1SupportMap { + conv := conversion + image.RegisterConverter(conv.compressed, conv.uncompressed, func(src []byte, w, h, d int) ([]byte, error) { + return image.Convert(src, w, h, d, ETC2_RGB_U8_NORM, conv.uncompressed) + }) + + image.RegisterConverter(conv.uncompressed, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + bytesPerPixel := 4 + return compress(src, w, h, d, conv.compressed, bytesPerPixel) + }) + } + + // This is for converting via intermediate format. + EACtoRGBASupportMap := []converterLayout{ + {image.R_U16_NORM, ETC2_R_U11_NORM}, + {image.R_S16_NORM, ETC2_R_S11_NORM}, + {image.RG_U16_NORM, ETC2_RG_U11_NORM}, + {image.RG_S16_NORM, ETC2_RG_S11_NORM}, + } + + for _, conversion := range EACtoRGBASupportMap { + // Intentional local copy + conv := conversion + if !image.Registered(conv.compressed, image.RGB_U8_NORM) { + image.RegisterConverter(conv.compressed, image.RGB_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { + rgb, err := image.Convert(src, w, h, d, conv.compressed, conv.uncompressed) + if err != nil { + return nil, err + } + return image.Convert(rgb, w, h, d, conv.uncompressed, image.RGB_U8_NORM) + }) + } + if !image.Registered(conv.compressed, image.RGBA_U8_NORM) { + image.RegisterConverter(conv.compressed, image.RGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { + rgba, err := image.Convert(src, w, h, d, conv.compressed, conv.uncompressed) + if err != nil { + return nil, err + } + return image.Convert(rgba, w, h, d, conv.uncompressed, image.RGBA_U8_NORM) + }) + } + + if !image.Registered(image.RGB_U8_NORM, conv.compressed) { + image.RegisterConverter(image.RGB_U8_NORM, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + rgb, err := image.Convert(src, w, h, d, image.RGB_U8_NORM, conv.uncompressed) + if err != nil { + return nil, err + } + return image.Convert(rgb, w, h, d, conv.uncompressed, conv.compressed) + }) + } + + if !image.Registered(image.RGBA_U8_NORM, conv.compressed) { + image.RegisterConverter(image.RGBA_U8_NORM, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + rgb, err := image.Convert(src, w, h, d, image.RGBA_U8_NORM, conv.uncompressed) + if err != nil { + return nil, err + } + return image.Convert(rgb, w, h, d, conv.uncompressed, conv.compressed) + }) + } + } + + // This is for converting via intermediate format. + RToLuminanceSupportMap := []converterLayout{ + {image.R_U16_NORM, ETC2_R_U11_NORM}, + {image.R_S16_NORM, ETC2_R_S11_NORM}, + } + + for _, conversion := range RToLuminanceSupportMap { + // Intentional local copy + conv := conversion + if !image.Registered(conv.compressed, image.Luminance_R32) { + image.RegisterConverter(conv.compressed, image.Luminance_R32, func(src []byte, w, h, d int) ([]byte, error) { + rgb, err := image.Convert(src, w, h, d, conv.compressed, conv.uncompressed) + if err != nil { + return nil, err + } + return image.Convert(rgb, w, h, d, conv.uncompressed, image.Luminance_R32) + }) + } + + if !image.Registered(image.Luminance_R32, conv.compressed) { + image.RegisterConverter(image.Luminance_R32, conv.compressed, func(src []byte, w, h, d int) ([]byte, error) { + rgb, err := image.Convert(src, w, h, d, image.Luminance_R32, conv.uncompressed) + if err != nil { + return nil, err + } + return image.Convert(rgb, w, h, d, conv.uncompressed, conv.compressed) + }) + } + } +} + +func compress(src []byte, width, height, depth int, format *image.Format, bytesPerPixel int) ([]byte, error) { + dstSliceSize := format.Size(width, height, 1) + srcSliceSize := width * height * bytesPerPixel + dst := make([]byte, dstSliceSize*depth) + + for z := 0; z < depth; z++ { + currentSrcSlice := src[srcSliceSize*z:] + currentDstSlice := dst[dstSliceSize*z:] + inputImageData := (unsafe.Pointer)(¤tSrcSlice[0]) + outputImageData := (unsafe.Pointer)(¤tDstSlice[0]) + + result := C.compress_etc( + (*C.uint8_t)(inputImageData), + (*C.uint8_t)(outputImageData), + (C.uint32_t)(width), + (C.uint32_t)(height), + (C.enum_etc_format)(formatToCEnum[format]), + ) + + if result != 0 { + errorCString := C.get_etc_error_string(result) + errorString := C.GoString(errorCString) + C.free((unsafe.Pointer)(errorCString)) + return nil, fmt.Errorf("ETC Compression Status: %s", errorString) + } + } + + return dst, nil +} diff --git a/core/image/etc/etc2.h b/core/image/etc/etc2.h new file mode 100644 index 0000000000..a0a1e63d87 --- /dev/null +++ b/core/image/etc/etc2.h @@ -0,0 +1,54 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +#ifndef ETC2_H_ +#define ETC2_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum etc_format { + // ETC2 Formats + ETC2_RGB_U8_NORM, + ETC2_RGBA_U8_NORM, + ETC2_RGBA_U8U8U8U1_NORM, + ETC2_SRGB_U8_NORM, + ETC2_SRGBA_U8_NORM, + ETC2_SRGBA_U8U8U8U1_NORM, + + // EAC Formats + ETC2_R_U11_NORM, + ETC2_RG_U11_NORM, + ETC2_R_S11_NORM, + ETC2_RG_S11_NORM, + + // ETC1 Format + ETC1_RGB_U8_NORM, +}; + +typedef uint32_t etc_error; + +etc_error compress_etc(const uint8_t* input_image, uint8_t* output_image, + uint32_t width, uint32_t height, enum etc_format format); + +char* get_etc_error_string(etc_error error_code); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif diff --git a/core/image/etc/etc2_compressor.cpp b/core/image/etc/etc2_compressor.cpp new file mode 100644 index 0000000000..8f4e385379 --- /dev/null +++ b/core/image/etc/etc2_compressor.cpp @@ -0,0 +1,148 @@ +// Copyright (C) 2021 Google Inc. +// +// 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 "etc2.h" +#include "third_party/etc2comp/EtcLib/Etc/EtcImage.h" + +#include + +static_assert(sizeof(etc_error) >= sizeof(Etc::Image::EncodingStatus), + "etc_error should superset of Etc::Image::EncodingStatus to " + "protect against overflow"); + +namespace { +const uint32_t MIN_JOBS = 8; +const uint32_t MAX_JOBS = 1024; +const float EFFORT = 10.0f; +const Etc::ErrorMetric ERROR_METRIC = Etc::ErrorMetric::NUMERIC; +} // namespace + +Etc::Image::Format convert_etc_format(etc_format format) { + switch (format) { + case ETC2_RGB_U8_NORM: + return Etc::Image::Format::RGB8; + case ETC2_RGBA_U8_NORM: + return Etc::Image::Format::RGBA8; + case ETC2_RGBA_U8U8U8U1_NORM: + return Etc::Image::Format::RGB8A1; + case ETC2_SRGB_U8_NORM: + return Etc::Image::Format::SRGB8; + case ETC2_SRGBA_U8_NORM: + return Etc::Image::Format::SRGBA8; + case ETC2_SRGBA_U8U8U8U1_NORM: + return Etc::Image::Format::SRGB8A1; + case ETC2_R_U11_NORM: + return Etc::Image::Format::R11; + case ETC2_RG_U11_NORM: + return Etc::Image::Format::RG11; + case ETC2_R_S11_NORM: + return Etc::Image::Format::SIGNED_R11; + case ETC2_RG_S11_NORM: + return Etc::Image::Format::SIGNED_RG11; + case ETC1_RGB_U8_NORM: + return Etc::Image::Format::ETC1; + default: + return Etc::Image::Format::UNKNOWN; + } +} + +void read_image(const uint8_t* input_image, uint32_t width, uint32_t height, + std::vector& output) { + const uint8_t BYTE_PER_PIXEL = 4; + for (uint32_t h = 0; h < height; ++h) { + const uint8_t* src = &input_image[(h * width) * BYTE_PER_PIXEL]; + for (uint32_t w = 0; w < width; ++w) { + output.push_back(std::move(Etc::ColorFloatRGBA::ConvertFromRGBA8( + src[0], src[1], src[2], src[3]))); + src += BYTE_PER_PIXEL; + } + } +} + +extern "C" etc_error compress_etc(const uint8_t* input_image, + uint8_t* output_image, uint32_t width, + uint32_t height, etc_format format) { + std::vector source_image; + source_image.reserve(width * height); + + read_image(input_image, width, height, source_image); + Etc::Image image(reinterpret_cast(source_image.data()), width, height, + ERROR_METRIC); + image.m_bVerboseOutput = false; + + auto image_format = convert_etc_format(format); + if (image_format == Etc::Image::Format::UNKNOWN) { + return static_cast( + Etc::Image::EncodingStatus::ERROR_UNKNOWN_FORMAT); + } + + auto status = + image.Encode(image_format, ERROR_METRIC, EFFORT, MIN_JOBS, MAX_JOBS); + // We don't need to care about warnings as compression only used for + // experiments. The users can act on warnings when they actually compress + // their textures with an appropiate compression tool. + if (status > Etc::Image::EncodingStatus::ERROR_THRESHOLD) { + return static_cast(status); + } + + memcpy(output_image, image.GetEncodingBits(), image.GetEncodingBitsBytes()); + return static_cast(Etc::Image::EncodingStatus::SUCCESS); +} + +extern "C" char* get_etc_error_string(etc_error error_code) { + // This function will cause a minor memory leak to be able to return all the + // errors produced. This function will never be called in a well behaving + // scenario. If this method is called, compression, therefore the underlying + // operation e.g. experiments will fail and program is likely to be closed + // soon after. + + char* error_string = (char*)calloc(512, sizeof(char)); + auto status = static_cast(error_code); + if (status == Etc::Image::EncodingStatus::SUCCESS) { + strcpy(error_string, "Compression Succeed"); + return error_string; + } + + char* current = error_string; + int written = sprintf(current, "["); + current += written; + + if (status > Etc::Image::EncodingStatus::ERROR_THRESHOLD) { + if (status & Etc::Image::EncodingStatus::ERROR_UNKNOWN_FORMAT) { + written = sprintf(current, "\"Error: Unknown Image Format\""); + current += written; + } + if (status & Etc::Image::EncodingStatus::ERROR_UNKNOWN_ERROR_METRIC) { + written = sprintf(current, "\"Error: Unknown Error Metric\""); + current += written; + } + if (status & Etc::Image::EncodingStatus::ERROR_ZERO_WIDTH_OR_HEIGHT) { + written = sprintf(current, "\"Error: Image width or height is zero\""); + current += written; + } + } + + if (status > Etc::Image::EncodingStatus::WARNING_THRESHOLD) { + written = sprintf(current, "\"Warning with the Encoding Status Bits: %d\"", + status); + current += written; + } + + sprintf(current, "]"); + + return error_string; +} diff --git a/core/image/etc/etc2_decompressor.go b/core/image/etc/etc2_decompressor.go new file mode 100644 index 0000000000..d51d6102da --- /dev/null +++ b/core/image/etc/etc2_decompressor.go @@ -0,0 +1,425 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package etc + +import ( + "bytes" + + "github.com/google/gapid/core/data/endian" + "github.com/google/gapid/core/image" + "github.com/google/gapid/core/math/sint" + "github.com/google/gapid/core/math/u64" + "github.com/google/gapid/core/os/device" +) + +func decodeETCBaseMulModTbl(v uint64) (base, mul int, modTbl [8]int) { + alphaModTbl := [16][8]int{ + {-3, -6, -9, -15, 2, 5, 8, 14}, + {-3, -7, -10, -13, 2, 6, 9, 12}, + {-2, -5, -8, -13, 1, 4, 7, 12}, + {-2, -4, -6, -13, 1, 3, 5, 12}, + {-3, -6, -8, -12, 2, 5, 7, 11}, + {-3, -7, -9, -11, 2, 6, 8, 10}, + {-4, -7, -8, -11, 3, 6, 7, 10}, + {-3, -5, -8, -11, 2, 4, 7, 10}, + {-2, -6, -8, -10, 1, 5, 7, 9}, + {-2, -5, -8, -10, 1, 4, 7, 9}, + {-2, -4, -8, -10, 1, 3, 7, 9}, + {-2, -5, -7, -10, 1, 4, 6, 9}, + {-3, -4, -7, -10, 2, 3, 6, 9}, + {-1, -2, -3, -10, 0, 1, 2, 9}, + {-4, -6, -8, -9, 3, 5, 7, 8}, + {-3, -5, -7, -9, 2, 4, 6, 8}, + } + // ┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┓ + // ┃ Base ┃Multiplier ┃Table Index┃ + // ┣━━┯━━┯━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┫ + // ┃₆₃│₆₂│₆₁│₆₀│₅₉│₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂┃₅₁│₅₀│₄₉│₄₈┃ + // ┖──┴──┴──┴──┴──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┚ + return int(v >> 56), + int((v >> 52) & 15), + alphaModTbl[(v>>48)&15] +} + +func decodeETC(src []byte, width, height, depth int, alphaMode image.FmtETC2_AlphaMode) ([]byte, error) { + dst := make([]byte, width*height*depth*4) + + blockWidth := sint.Max((width+3)/4, 1) + blockHeight := sint.Max((height+3)/4, 1) + + const ( + R = 0 + G = 1 + B = 2 + ) + c := [4][3]int{} + codes := [2][4]int{} + modTbl0 := [2][8][4]int{ // differential mode + { // when opaque == 0: + {0, 8, 0, -8}, + {0, 17, 0, -17}, + {0, 29, 0, -29}, + {0, 42, 0, -42}, + {0, 60, 0, -60}, + {0, 80, 0, -80}, + {0, 106, 0, -106}, + {0, 183, 0, -183}, + }, { // when opaque == 1: + {2, 8, -2, -8}, + {5, 17, -5, -17}, + {9, 29, -9, -29}, + {13, 42, -13, -42}, + {18, 60, -18, -60}, + {24, 80, -24, -80}, + {33, 106, -33, -106}, + {47, 183, -47, -183}, + }, + } + modTbl1 := [8]int{3, 6, 11, 16, 23, 32, 41, 64} + diffTbl := [8]int{0, 1, 2, 3, -4, -3, -2, -1} + flipTbl := [2][16]int{ + {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, + {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}, + } + alpha := [16]byte{ + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, + } + + r := endian.Reader(bytes.NewReader(src), device.BigEndian) + + for z := 0; z < depth; z++ { + dst := dst[z*width*height*4:] + for by := 0; by < blockHeight; by++ { + for bx := 0; bx < blockWidth; bx++ { + if alphaMode == image.FmtETC2_ALPHA_8BIT { + v64 := r.Uint64() + base, mul, modTbl := decodeETCBaseMulModTbl(v64) + for i := uint8(0); i < 16; i++ { + mod := modTbl[(v64>>(i*3))&7] + alpha[15-i] = sint.Byte(base + mod*mul) + } + } + + v64 := r.Uint64() + flip := (v64 >> 32) & 1 + diff := (v64 >> 33) & 1 + opaque := 1 + if alphaMode == image.FmtETC2_ALPHA_1BIT { + opaque = int(diff) + } + + mode := uint(0) + for i := uint(0); i < 3; i++ { + if alphaMode != image.FmtETC2_ALPHA_1BIT && diff == 0 { + // ┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━┳━━┓ + // ┃ R₀ ┃ R₁ ┃ G₀ ┃ G₁ ┃ B₀ ┃ B₁ ┃ C₀ ┃ C₁ ┃df┃fp┃ + // ┣━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━╋━━┫ + // ┃₆₃│₆₂│₆₁│₆₀┃₅₉│₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂┃₅₁│₅₀│₄₉│₄₈┃₄₇│₄₆│₄₅│₄₄┃₄₃│₄₂│₄₁│₄₀┃₃₉│₃₈│₃₇┃₃₆│₃₅│₃₄┃₃₃┃₃₂┃ + // ┖──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┸──┴──┴──┸──┸──┚ + a := (v64 >> (60 - i*8)) & 15 + b := (v64 >> (56 - i*8)) & 15 + c[0][i] = int((a << 4) | a) + c[1][i] = int((b << 4) | b) + } else { + // ┏━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━┳━━┓ + // ┃ R₀ ┃Delta R₀┃ G₀ ┃Delta G₀┃ B₀ ┃Delta B₀┃ C₀ ┃ C₁ ┃df┃fp┃ + // ┣━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━╋━━┫ + // ┃₆₃│₆₂│₆₁│₆₀│₅₉┃₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂│₅₁┃₅₀│₄₉│₄₈┃₄₇│₄₆│₄₅│₄₄│₄₃┃₄₂│₄₁│₄₀┃₃₉│₃₈│₃₇┃₃₆│₃₅│₃₄┃₃₃┃₃₂┃ + // ┖──┴──┴──┴──┴──┸──┴──┴──┸──┴──┴──┴──┴──┸──┴──┴──┸──┴──┴──┴──┴──┸──┴──┴──┸──┴──┴──┸──┴──┴──┸──┸──┚ + a := (v64 >> (59 - i*8)) & 31 + d := (v64 >> (56 - i*8)) & 7 + b := int(a) + diffTbl[d] + if b < 0 || b > 31 { + mode = i + 1 + break + } + c[0][i] = int((a << 3) | (a >> 2)) + c[1][i] = int((b << 3) | (b >> 2)) + } + } + + switch mode { + case 0: // individual & differential mode (ETC1) + codes[0] = modTbl0[opaque][(v64>>37)&7] + codes[1] = modTbl0[opaque][(v64>>34)&7] + + blockTbl := flipTbl[flip] + + i := uint(0) + for x := bx * 4; x < (bx+1)*4; x++ { + for y := by * 4; y < (by+1)*4; y++ { + if x < width && y < height { + block := blockTbl[i] + k := 4 * (y*width + x) + idx := ((v64 >> i) & 1) | ((v64 >> (15 + i)) & 2) + if opaque == 0 && idx == 2 { + dst[k+2] = 0 + dst[k+1] = 0 + dst[k+0] = 0 + dst[k+3] = 0 + } else { + base := c[block] + shift := codes[block][idx] + dst[k+2] = sint.Byte(base[2] + shift) + dst[k+1] = sint.Byte(base[1] + shift) + dst[k+0] = sint.Byte(base[0] + shift) + dst[k+3] = alpha[i] + } + } + i++ + } + } + case 1: // T-mode + // ┏━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┓ + // ┃ ┃ R₀ ┏━━┓ ┃ G₀ ┃ B₀ ┃ R₂ ┃ G₂ ┃ B₂ ┃ ┏━━┓ ┃ + // ┣━━┯━━┯━━╋━━┯━━╋━━╋━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━╋━━╋━━┫ + // ┃₆₃│₆₂│₆₁┃₆₀│₅₉┃₅₈┃₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂┃₅₁│₅₀│₄₉│₄₈┃₄₇│₄₆│₄₅│₄₄┃₄₃│₄₂│₄₁│₄₀┃₃₉│₃₈│₃₇│₃₆┃₃₅│₃₄┃₃₃┃₃₂┃ + // ┖──┴──┴──┸──┴──┸──┸──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┸──┸──┚ + + // Load colors + c[0][R] = int(u64.Expand4to8(((v64 >> 57) & 12) | (v64>>56)&3)) + c[0][G] = int(u64.Expand4to8(v64 >> 52)) + c[0][B] = int(u64.Expand4to8(v64 >> 48)) + c[2][R] = int(u64.Expand4to8(v64 >> 44)) + c[2][G] = int(u64.Expand4to8(v64 >> 40)) + c[2][B] = int(u64.Expand4to8(v64 >> 36)) + + // Load intensity modifier + modIdx := ((v64 >> 33) & 6) | ((v64 >> 32) & 1) + mod := modTbl1[modIdx] + // Calculate C₁ and c₃ + for i := 0; i < 3; i++ { + c[1][i] = c[2][i] + mod + c[3][i] = c[2][i] - mod + } + // ┏━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┓ + // ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₃ ┃ x₃ ┃ x₃ ┃ x₃ ┃ + // ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ + // ┣━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━┫ + // ┃₃₁│₃₀┃₂₉│₂₈┃₂₇│₂₆┃₂₅│₂₄┃₂₃│₂₂┃₂₁│₂₀┃₁₉│₁₈┃₁₇│₁₆┃₁₅│₁₄┃₁₃│₁₂┃₁₁│₁₀┃ ₉│ ₈┃ ₇│ ₆┃ ₅│ ₄┃ ₃│ ₂┃ ₁│ ₀┃ + // ┖──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┚ + // Use 2-bit indices to lookup texel colors + i := uint(0) + for x := bx * 4; x < (bx+1)*4; x++ { + for y := by * 4; y < (by+1)*4; y++ { + if x < width && y < height { + k := 4 * (y*width + x) + idx := ((v64 >> i) & 1) | ((v64 >> (15 + i)) & 2) + if opaque == 0 && idx == 2 { + dst[k+0] = 0 + dst[k+1] = 0 + dst[k+2] = 0 + dst[k+3] = 0 + } else { + dst[k+0] = sint.Byte(c[idx][0]) + dst[k+1] = sint.Byte(c[idx][1]) + dst[k+2] = sint.Byte(c[idx][2]) + dst[k+3] = alpha[i] + } + } + i++ + } + } + case 2: // H-mode + // ┏━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┓ + // ┃ ┃ R₀ ┃ G₀ ┏━━━━━━━━┓ ┃ ┏━━┓ B₀ ┃ R₂ ┃ G₂ ┃ B₂ ┃md┏━━┓ ┃ + // ┣━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━╋━━╋━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━╋━━╋━━┫ + // ┃₆₃┃₆₂│₆₁│₆₀│₅₉┃₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃┃₅₂┃₅₁┃₅₀┃₄₉│₄₈│₄₇┃₄₆│₄₅│₄₄│₄₃┃₄₂│₄₁│₄₀│₃₉┃₃₈│₃₇│₃₆│₃₅┃₃₄┃₃₃┃₃₂┃ + // ┖──┸──┴──┴──┴──┸──┴──┴──┸──┴──┴──┸──┸──┸──┸──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┸──┸──┚ + + // Load colors + c[0][R] = int(u64.Expand4to8(v64 >> 59)) + c[0][G] = int(u64.Expand4to8(((v64 >> 55) & 14) | ((v64 >> 52) & 1))) + c[0][B] = int(u64.Expand4to8(((v64 >> 48) & 8) | ((v64 >> 47) & 7))) + c[2][R] = int(u64.Expand4to8(v64 >> 43)) + c[2][G] = int(u64.Expand4to8(v64 >> 39)) + c[2][B] = int(u64.Expand4to8(v64 >> 35)) + + // Load intensity modifier + modIdx := ((v64 >> 32) & 4) | ((v64 >> 31) & 2) + // LSB of modIdx is 1 if: + if (c[0][R]<<16)+(c[0][G]<<8)+c[0][B] >= (c[2][R]<<16)+(c[2][G]<<8)+c[2][B] { + modIdx++ + } + mod := modTbl1[modIdx] + // Calculate C₁ and c₃ + for i := 0; i < 3; i++ { + c[0][i], c[1][i] = c[0][i]+mod, c[0][i]-mod + c[2][i], c[3][i] = c[2][i]+mod, c[2][i]-mod + } + // ┏━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┓ + // ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₃ ┃ x₃ ┃ x₃ ┃ x₃ ┃ + // ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ + // ┣━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━┫ + // ┃₃₁│₃₀┃₂₉│₂₈┃₂₇│₂₆┃₂₅│₂₄┃₂₃│₂₂┃₂₁│₂₀┃₁₉│₁₈┃₁₇│₁₆┃₁₅│₁₄┃₁₃│₁₂┃₁₁│₁₀┃ ₉│ ₈┃ ₇│ ₆┃ ₅│ ₄┃ ₃│ ₂┃ ₁│ ₀┃ + // ┖──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┚ + // Use 2-bit indices to lookup texel colors + i := uint(0) + for x := bx * 4; x < (bx+1)*4; x++ { + for y := by * 4; y < (by+1)*4; y++ { + if x < width && y < height { + k := 4 * (y*width + x) + idx := ((v64 >> i) & 1) | ((v64 >> (15 + i)) & 2) + if opaque == 0 && idx == 2 { + dst[k+0] = 0 + dst[k+1] = 0 + dst[k+2] = 0 + dst[k+3] = 0 + } else { + dst[k+0] = sint.Byte(c[idx][0]) + dst[k+1] = sint.Byte(c[idx][1]) + dst[k+2] = sint.Byte(c[idx][2]) + dst[k+3] = alpha[i] + } + } + i++ + } + } + case 3: // planar-mode + // ┏━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ + // ┃ ┃ R₀ ┃ ┏━━┓ G₀ ┃ ┏━━━━━━━━┓ B₀ ┏━━┓ ┃ R₁ ┏━━┓ ┃ + // ┣━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━╋━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━╋━━┯━━┯━━╋━━┯━━╋━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━╋━━╋━━┫ + // ┃₆₃┃₆₂│₆₁│₆₀│₅₉│₅₈│₅₇┃₅₆┃₅₅┃₅₄│₅₃│₅₂│₅₁│₅₀│₄₉┃₄₈┃₄₇│₄₆│₄₅┃₄₄│₄₃┃₄₂┃₄₁│₄₀│₃₉┃₃₈│₃₇│₃₆│₃₅│₃₄┃₃₃┃₃₂┃ + // ┖──┸──┴──┴──┴──┴──┴──┸──┸──┸──┴──┴──┴──┴──┴──┸──┸──┴──┴──┸──┴──┸──┸──┴──┴──┸──┴──┴──┴──┴──┸──┸──┚ + // + // ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ + // ┃ G₁ ┃ B₁ ┃ R₂ ┃ G₂ ┃ B₂ ┃ + // ┣━━┯━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━┫ + // ┃₃₁│₃₀│₂₉│₂₈│₂₇│₂₆│₂₅┃₂₄│₂₃│₂₂│₂₁│₂₀│₁₉┃₁₈│₁₇│₁₆│₁₅│₁₄│₁₃┃₁₂│₁₁│₁₀│ ₉│ ₈│ ₇│ ₆┃ ₅│ ₄│ ₃│ ₂│ ₁│ ₀┃ + // ┖──┴──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┚ + + // Load colors + c[0][R] = int(u64.Expand6to8(v64 >> 57)) + c[0][G] = int(u64.Expand7to8(((v64 >> 50) & 64) | ((v64 >> 49) & 63))) + c[0][B] = int(u64.Expand6to8(((v64 >> 43) & 32) | ((v64 >> 40) & 24) | ((v64 >> 39) & 7))) + + c[1][R] = int(u64.Expand6to8(((v64 >> 33) & 62) | ((v64 >> 32) & 1))) + c[1][G] = int(u64.Expand7to8(v64 >> 25)) + c[1][B] = int(u64.Expand6to8(v64 >> 19)) + + c[2][R] = int(u64.Expand6to8(v64 >> 13)) + c[2][G] = int(u64.Expand7to8(v64 >> 6)) + c[2][B] = int(u64.Expand6to8(v64)) + + i := 0 + for dx := 0; dx < 4; dx++ { + x := bx*4 + dx + for dy := 0; dy < 4; dy++ { + y := by*4 + dy + if x < width && y < height { + k := 4 * (y*width + x) + dst[k+0] = sint.Byte((dx*(c[1][R]-c[0][R]) + dy*(c[2][R]-c[0][R]) + 4*c[0][R] + 2) >> 2) + dst[k+1] = sint.Byte((dx*(c[1][G]-c[0][G]) + dy*(c[2][G]-c[0][G]) + 4*c[0][G] + 2) >> 2) + dst[k+2] = sint.Byte((dx*(c[1][B]-c[0][B]) + dy*(c[2][B]-c[0][B]) + 4*c[0][B] + 2) >> 2) + dst[k+3] = alpha[i] + } + i++ + } + } + } + } + } + } + + return dst, nil +} + +func decodeETCU11(src []byte, width, height, depth int, channels int) ([]byte, error) { + dst := make([]byte, width*height*depth*channels*2) + blockWidth := sint.Max((width+3)/4, 1) + blockHeight := sint.Max((height+3)/4, 1) + r := endian.Reader(bytes.NewReader(src), device.BigEndian) + + for z := 0; z < depth; z++ { + dst := dst[z*width*height*channels*2:] + for by := 0; by < blockHeight; by++ { + for bx := 0; bx < blockWidth; bx++ { + for c := 0; c < channels; c++ { + v64 := r.Uint64() + base, mul, modTbl := decodeETCBaseMulModTbl(v64) + if mul != 0 { + mul *= 8 + } else { + mul = 1 + } + i := uint(15) + for x := bx * 4; x < bx*4+4; x++ { + for y := by * 4; y < by*4+4; y++ { + if x < width && y < height { + mod := modTbl[(v64>>(i*3))&7] + u11 := uint(sint.Clamp(base*8+4+mod*mul, 0, 2047)) + u16 := (u11 << 5) | (u11 >> 5) + k := 2*channels*(y*width+x) + c*2 + dst[k+0] = byte(u16) + dst[k+1] = byte(u16 >> 8) + } + i-- + } + } + } + } + } + } + + return dst, nil +} + +func decodeETCS11(src []byte, width, height, depth int, channels int) ([]byte, error) { + dst := make([]byte, width*height*depth*channels*2) + blockWidth := sint.Max((width+3)/4, 1) + blockHeight := sint.Max((height+3)/4, 1) + r := endian.Reader(bytes.NewReader(src), device.BigEndian) + for z := 0; z < depth; z++ { + dst := dst[z*width*height*channels*2:] + for by := 0; by < blockHeight; by++ { + for bx := 0; bx < blockWidth; bx++ { + for c := 0; c < channels; c++ { + v64 := r.Uint64() + base, mul, modTbl := decodeETCBaseMulModTbl(v64) + base = int(int8(base)) + if mul != 0 { + mul *= 8 + } else { + mul = 1 + } + i := uint(15) + for x := bx * 4; x < bx*4+4; x++ { + for y := by * 4; y < by*4+4; y++ { + if x < width && y < height { + mod := modTbl[(v64>>(i*3))&7] + s11 := sint.Clamp(base*8+mod*mul, -1023, 1023) + var s16 int + if s11 >= 0 { + s16 = (s11 << 5) | (s11 >> 5) + } else { + s16 = -((-s11 << 5) | (-s11 >> 5)) + } + k := 2*channels*(y*width+x) + c*2 + dst[k+0] = byte(s16) + dst[k+1] = byte(s16 >> 8) + } + i-- + } + } + } + } + } + } + + return dst, nil +} diff --git a/core/image/etc1.go b/core/image/etc1.go index cbfa872cc7..44fb78f8d9 100644 --- a/core/image/etc1.go +++ b/core/image/etc1.go @@ -19,10 +19,6 @@ import ( "github.com/google/gapid/core/stream" ) -var ( - ETC1_RGB_U8_NORM = NewETC1_RGB_U8_NORM("ETC1_RGB_U8_NORM") -) - // NewETC1_RGB_U8_NORM returns a format representing the ETC1_RGB8 block texture // compression format. func NewETC1_RGB_U8_NORM(name string) *Format { @@ -41,12 +37,3 @@ func (f *FmtETC1_RGB_U8_NORM) check(data []byte, w, h, d int) error { func (*FmtETC1_RGB_U8_NORM) channels() stream.Channels { return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue} } - -func init() { - RegisterConverter(ETC1_RGB_U8_NORM, RGB_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return Convert(src, w, h, d, ETC2_RGB_U8_NORM, RGB_U8_NORM) - }) - RegisterConverter(ETC1_RGB_U8_NORM, RGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return Convert(src, w, h, d, ETC2_RGB_U8_NORM, RGBA_U8_NORM) - }) -} diff --git a/core/image/etc2.go b/core/image/etc2.go index 32964c466f..2218ba28a9 100644 --- a/core/image/etc2.go +++ b/core/image/etc2.go @@ -15,651 +15,66 @@ package image import ( - "bytes" + "fmt" - "github.com/google/gapid/core/data/endian" "github.com/google/gapid/core/math/sint" - "github.com/google/gapid/core/math/u64" - "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/stream" ) -var ( - ETC2_RGB_U8_NORM = NewETC2_RGB_U8_NORM("ETC2_RGB_U8_NORM") - ETC2_RGBA_U8_NORM = NewETC2_RGBA_U8_NORM("ETC2_RGBA_U8_NORM") - ETC2_RGBA_U8U8U8U1_NORM = NewETC2_RGBA_U8U8U8U1_NORM("ETC2_RGBA_U8U8U8U1_NORM") - ETC2_SRGB_U8_NORM = NewETC2_SRGB_U8_NORM("ETC2_SRGB_U8_NORM") - ETC2_SRGBA_U8_NORM = NewETC2_SRGBA_U8_NORM("ETC2_SRGBA_U8_NORM") - ETC2_SRGBA_U8U8U8U1_NORM = NewETC2_SRGBA_U8U8U8U1_NORM("ETC2_SRGBA_U8U8U8U1_NORM") - ETC2_R_U11_NORM = NewETC2_R_U11_NORM("ETC2_R_U11_NORM") - ETC2_RG_U11_NORM = NewETC2_RG_U11_NORM("ETC2_RG_U11_NORM") - ETC2_R_S11_NORM = NewETC2_R_S11_NORM("ETC2_R_S11_NORM") - ETC2_RG_S11_NORM = NewETC2_RG_S11_NORM("ETC2_RG_S11_NORM") -) - -// NewETC2_RGB_U8_NORM returns a format representing the COMPRESSED_RGB8_ETC2 -// block texture compression format. -func NewETC2_RGB_U8_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgbU8Norm{&FmtETC2_RGB_U8_NORM{}}} -} - -// NewETC2_SRGB_U8_NORM returns a format representing the COMPRESSED_SRGB8_ETC2 -// block texture compression format. -func NewETC2_SRGB_U8_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgbU8Norm{&FmtETC2_RGB_U8_NORM{Srgb: true}}} -} - -func (f *FmtETC2_RGB_U8_NORM) key() interface{} { - return "ETC2_RGB_U8_NORM" -} -func (*FmtETC2_RGB_U8_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) / 2 -} -func (f *FmtETC2_RGB_U8_NORM) check(data []byte, w, h, d int) error { - return checkSize(data, f, w, h, d) -} -func (*FmtETC2_RGB_U8_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue} -} - -// NewETC2_RGBA_U8_NORM returns a format representing the -// COMPRESSED_RGBA8_ETC2_EAC block texture compression format. -func NewETC2_RGBA_U8_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgbaU8Norm{&FmtETC2_RGBA_U8_NORM{}}} -} - -// NewETC2_SRGBA_U8_NORM returns a format representing the -// COMPRESSED_SRGBA8_ETC2_EAC block texture compression format. -func NewETC2_SRGBA_U8_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgbaU8Norm{&FmtETC2_RGBA_U8_NORM{Srgb: true}}} -} - -func (f *FmtETC2_RGBA_U8_NORM) key() interface{} { - return "ETC2_RGBA_U8_NORM" -} -func (*FmtETC2_RGBA_U8_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) -} -func (f *FmtETC2_RGBA_U8_NORM) check(data []byte, w, h, d int) error { - return checkSize(data, f, w, h, d) -} -func (*FmtETC2_RGBA_U8_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue, stream.Channel_Alpha} -} - -// NewETC2_RGBA_U8U8U8U1_NORM returns a format representing the -// COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 block texture compression format. -func NewETC2_RGBA_U8U8U8U1_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgbaU8U8U8U1Norm{&FmtETC2_RGBA_U8U8U8U1_NORM{}}} -} - -// NewETC2_SRGBA_U8U8U8U1_NORM returns a format representing the -// COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 block texture compression format. -func NewETC2_SRGBA_U8U8U8U1_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgbaU8U8U8U1Norm{&FmtETC2_RGBA_U8U8U8U1_NORM{Srgb: true}}} -} - -func (f *FmtETC2_RGBA_U8U8U8U1_NORM) key() interface{} { - return "ETC2_RGBA_U8U8U8U1_NORM" -} -func (*FmtETC2_RGBA_U8U8U8U1_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) / 2 -} -func (f *FmtETC2_RGBA_U8U8U8U1_NORM) check(data []byte, w, h, d int) error { - return checkSize(data, f, w, h, d) -} -func (*FmtETC2_RGBA_U8U8U8U1_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue, stream.Channel_Alpha} -} - -// NewETC2_R_U11_NORM returns a format representing the COMPRESSED_R11_EAC -// block texture compression format. -func NewETC2_R_U11_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RU11Norm{&FmtETC2_R_U11_NORM{}}} -} - -func (f *FmtETC2_R_U11_NORM) key() interface{} { - return "ETC2_R_U11_NORM" -} -func (*FmtETC2_R_U11_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) / 2 -} -func (f *FmtETC2_R_U11_NORM) check(data []byte, w, h, d int) error { - return checkSize(data, f, w, h, d) -} -func (*FmtETC2_R_U11_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red} +func NewETC2(name string, colorMode FmtETC2_ColorMode, alphaMode FmtETC2_AlphaMode) *Format { + return &Format{ + Name: name, + Format: &Format_Etc2{ + &FmtETC2{ + ColorMode: colorMode, + AlphaMode: alphaMode, + }, + }, + } } -// NewETC2_RG_U11_NORM returns a format representing the COMPRESSED_RG11_EAC -// block texture compression format. -func NewETC2_RG_U11_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgU11Norm{&FmtETC2_RG_U11_NORM{}}} +func (f *FmtETC2) key() interface{} { + // If enum value is 0, it's not included in f.String() + // causes FmtETC2_R11's string to be empty + return fmt.Sprintf("FmtETCS{ColorMode:%v, AlphaMode:%v}", f.ColorMode, f.AlphaMode) } -func (f *FmtETC2_RG_U11_NORM) key() interface{} { - return "ETC2_RG_U11_NORM" -} -func (*FmtETC2_RG_U11_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) -} -func (f *FmtETC2_RG_U11_NORM) check(data []byte, w, h, d int) error { - return checkSize(data, f, w, h, d) -} -func (*FmtETC2_RG_U11_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red, stream.Channel_Green} -} +func (f *FmtETC2) size(w, h, d int) int { + baseSize := d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) -// NewETC2_R_S11_NORM returns a format representing the -// COMPRESSED_SIGNED_R11_EAC block texture compression format. -func NewETC2_R_S11_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RS11Norm{&FmtETC2_R_S11_NORM{}}} -} + // In ETC2 RGBA8 and RG11 compressed with 128 bit per 4x4 block. + // All the others have 64 bit. Therefore half the size. + if (f.ColorMode == FmtETC2_RGB || f.ColorMode == FmtETC2_SRGB) && f.AlphaMode == FmtETC2_ALPHA_8BIT { + return baseSize + } -func (f *FmtETC2_R_S11_NORM) key() interface{} { - return "ETC2_R_S11_NORM" -} -func (*FmtETC2_R_S11_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) / 2 -} -func (f *FmtETC2_R_S11_NORM) check(data []byte, w, h, d int) error { - return checkSize(data, f, w, h, d) -} -func (*FmtETC2_R_S11_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red} -} + if f.ColorMode == FmtETC2_RG || f.ColorMode == FmtETC2_RG_SIGNED { + return baseSize + } -// NewETC2_RG_S11_NORM returns a format representing the COMPRESSED_RG11_EAC -// block texture compression format. -func NewETC2_RG_S11_NORM(name string) *Format { - return &Format{Name: name, Format: &Format_Etc2RgS11Norm{&FmtETC2_RG_S11_NORM{}}} + return baseSize / 2 } -func (f *FmtETC2_RG_S11_NORM) key() interface{} { - return "ETC2_RG_S11_NORM" -} -func (*FmtETC2_RG_S11_NORM) size(w, h, d int) int { - return d * (sint.Max(sint.AlignUp(w, 4), 4) * sint.Max(sint.AlignUp(h, 4), 4)) -} -func (f *FmtETC2_RG_S11_NORM) check(data []byte, w, h, d int) error { +func (f *FmtETC2) check(data []byte, w, h, d int) error { return checkSize(data, f, w, h, d) } -func (*FmtETC2_RG_S11_NORM) channels() stream.Channels { - return stream.Channels{stream.Channel_Red, stream.Channel_Green} -} - -func init() { - RegisterConverter(ETC2_RGB_U8_NORM, RGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETC(src, w, h, d, etcAlphaNone) - }) - RegisterConverter(ETC2_RGBA_U8_NORM, RGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETC(src, w, h, d, etcAlpha8Bit) - }) - RegisterConverter(ETC2_RGBA_U8U8U8U1_NORM, RGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETC(src, w, h, d, etcAlpha1Bit) - }) - RegisterConverter(ETC2_SRGB_U8_NORM, SRGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETC(src, w, h, d, etcAlphaNone) - }) - RegisterConverter(ETC2_SRGBA_U8_NORM, SRGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETC(src, w, h, d, etcAlpha8Bit) - }) - RegisterConverter(ETC2_SRGBA_U8U8U8U1_NORM, SRGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETC(src, w, h, d, etcAlpha1Bit) - }) - RegisterConverter(ETC2_R_U11_NORM, R_U16_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETCU11(src, w, h, d, 1) - }) - RegisterConverter(ETC2_RG_U11_NORM, RG_U16_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETCU11(src, w, h, d, 2) - }) - RegisterConverter(ETC2_R_S11_NORM, R_S16_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETCS11(src, w, h, d, 1) - }) - RegisterConverter(ETC2_RG_S11_NORM, RG_S16_NORM, func(src []byte, w, h, d int) ([]byte, error) { - return decodeETCS11(src, w, h, d, 2) - }) - - for _, conv := range []struct { - src, dst *Format - }{ - {ETC2_RGB_U8_NORM, RGB_U8_NORM}, - {ETC2_RGBA_U8_NORM, RGBA_U8_NORM}, - {ETC2_RGBA_U8U8U8U1_NORM, RGBA_U8_NORM}, - {ETC2_SRGB_U8_NORM, SRGBA_U8_NORM}, - {ETC2_SRGBA_U8_NORM, SRGBA_U8_NORM}, - {ETC2_SRGBA_U8U8U8U1_NORM, SRGBA_U8_NORM}, - {ETC2_R_U11_NORM, R_U16_NORM}, - {ETC2_RG_U11_NORM, RG_U16_NORM}, - {ETC2_R_S11_NORM, R_S16_NORM}, - {ETC2_RG_S11_NORM, RG_S16_NORM}, - } { - conv := conv - if !registered(conv.src, RGB_U8_NORM) { - RegisterConverter(conv.src, RGB_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - rgb, err := Convert(src, w, h, d, conv.src, conv.dst) - if err != nil { - return nil, err - } - return Convert(rgb, w, h, d, conv.dst, RGB_U8_NORM) - }) - } - if !registered(conv.src, RGBA_U8_NORM) { - RegisterConverter(conv.src, RGBA_U8_NORM, func(src []byte, w, h, d int) ([]byte, error) { - rgba, err := Convert(src, w, h, d, conv.src, conv.dst) - if err != nil { - return nil, err - } - return Convert(rgba, w, h, d, conv.dst, RGBA_U8_NORM) - }) - } - } -} -func decodeETCBaseMulModTbl(v uint64) (base, mul int, modTbl [8]int) { - alphaModTbl := [16][8]int{ - {-3, -6, -9, -15, 2, 5, 8, 14}, - {-3, -7, -10, -13, 2, 6, 9, 12}, - {-2, -5, -8, -13, 1, 4, 7, 12}, - {-2, -4, -6, -13, 1, 3, 5, 12}, - {-3, -6, -8, -12, 2, 5, 7, 11}, - {-3, -7, -9, -11, 2, 6, 8, 10}, - {-4, -7, -8, -11, 3, 6, 7, 10}, - {-3, -5, -8, -11, 2, 4, 7, 10}, - {-2, -6, -8, -10, 1, 5, 7, 9}, - {-2, -5, -8, -10, 1, 4, 7, 9}, - {-2, -4, -8, -10, 1, 3, 7, 9}, - {-2, -5, -7, -10, 1, 4, 6, 9}, - {-3, -4, -7, -10, 2, 3, 6, 9}, - {-1, -2, -3, -10, 0, 1, 2, 9}, - {-4, -6, -8, -9, 3, 5, 7, 8}, - {-3, -5, -7, -9, 2, 4, 6, 8}, +func (f *FmtETC2) channels() stream.Channels { + if f.ColorMode == FmtETC2_R || f.ColorMode == FmtETC2_R_SIGNED { + return stream.Channels{stream.Channel_Red} } - // ┏━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┓ - // ┃ Base ┃Multiplier ┃Table Index┃ - // ┣━━┯━━┯━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┫ - // ┃₆₃│₆₂│₆₁│₆₀│₅₉│₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂┃₅₁│₅₀│₄₉│₄₈┃ - // ┖──┴──┴──┴──┴──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┚ - return int(v >> 56), - int((v >> 52) & 15), - alphaModTbl[(v>>48)&15] -} - -type etcAlphaMode int - -const ( - etcAlphaNone = etcAlphaMode(iota) - etcAlpha8Bit - etcAlpha1Bit -) -func decodeETC(src []byte, width, height, depth int, alphaMode etcAlphaMode) ([]byte, error) { - dst := make([]byte, width*height*depth*4) - - blockWidth := sint.Max((width+3)/4, 1) - blockHeight := sint.Max((height+3)/4, 1) - - const ( - R = 0 - G = 1 - B = 2 - ) - c := [4][3]int{} - codes := [2][4]int{} - modTbl0 := [2][8][4]int{ // differential mode - { // when opaque == 0: - {0, 8, 0, -8}, - {0, 17, 0, -17}, - {0, 29, 0, -29}, - {0, 42, 0, -42}, - {0, 60, 0, -60}, - {0, 80, 0, -80}, - {0, 106, 0, -106}, - {0, 183, 0, -183}, - }, { // when opaque == 1: - {2, 8, -2, -8}, - {5, 17, -5, -17}, - {9, 29, -9, -29}, - {13, 42, -13, -42}, - {18, 60, -18, -60}, - {24, 80, -24, -80}, - {33, 106, -33, -106}, - {47, 183, -47, -183}, - }, + if f.ColorMode == FmtETC2_RG || f.ColorMode == FmtETC2_RG_SIGNED { + return stream.Channels{stream.Channel_Red, stream.Channel_Green} } - modTbl1 := [8]int{3, 6, 11, 16, 23, 32, 41, 64} - diffTbl := [8]int{0, 1, 2, 3, -4, -3, -2, -1} - flipTbl := [2][16]int{ - {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}, - {0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1}, - } - alpha := [16]byte{ - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, 0xff, 0xff, - } - - r := endian.Reader(bytes.NewReader(src), device.BigEndian) - for z := 0; z < depth; z++ { - dst := dst[z*width*height*4:] - for by := 0; by < blockHeight; by++ { - for bx := 0; bx < blockWidth; bx++ { - if alphaMode == etcAlpha8Bit { - v64 := r.Uint64() - base, mul, modTbl := decodeETCBaseMulModTbl(v64) - for i := uint8(0); i < 16; i++ { - mod := modTbl[(v64>>(i*3))&7] - alpha[15-i] = sint.Byte(base + mod*mul) - } - } - - v64 := r.Uint64() - flip := (v64 >> 32) & 1 - diff := (v64 >> 33) & 1 - opaque := 1 - if alphaMode == etcAlpha1Bit { - opaque = int(diff) - } - - mode := uint(0) - for i := uint(0); i < 3; i++ { - if alphaMode != etcAlpha1Bit && diff == 0 { - // ┏━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━┳━━┓ - // ┃ R₀ ┃ R₁ ┃ G₀ ┃ G₁ ┃ B₀ ┃ B₁ ┃ C₀ ┃ C₁ ┃df┃fp┃ - // ┣━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━╋━━┫ - // ┃₆₃│₆₂│₆₁│₆₀┃₅₉│₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂┃₅₁│₅₀│₄₉│₄₈┃₄₇│₄₆│₄₅│₄₄┃₄₃│₄₂│₄₁│₄₀┃₃₉│₃₈│₃₇┃₃₆│₃₅│₃₄┃₃₃┃₃₂┃ - // ┖──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┸──┴──┴──┸──┸──┚ - a := (v64 >> (60 - i*8)) & 15 - b := (v64 >> (56 - i*8)) & 15 - c[0][i] = int((a << 4) | a) - c[1][i] = int((b << 4) | b) - } else { - // ┏━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━┳━━┓ - // ┃ R₀ ┃Delta R₀┃ G₀ ┃Delta G₀┃ B₀ ┃Delta B₀┃ C₀ ┃ C₁ ┃df┃fp┃ - // ┣━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━╋━━┫ - // ┃₆₃│₆₂│₆₁│₆₀│₅₉┃₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂│₅₁┃₅₀│₄₉│₄₈┃₄₇│₄₆│₄₅│₄₄│₄₃┃₄₂│₄₁│₄₀┃₃₉│₃₈│₃₇┃₃₆│₃₅│₃₄┃₃₃┃₃₂┃ - // ┖──┴──┴──┴──┴──┸──┴──┴──┸──┴──┴──┴──┴──┸──┴──┴──┸──┴──┴──┴──┴──┸──┴──┴──┸──┴──┴──┸──┴──┴──┸──┸──┚ - a := (v64 >> (59 - i*8)) & 31 - d := (v64 >> (56 - i*8)) & 7 - b := int(a) + diffTbl[d] - if b < 0 || b > 31 { - mode = i + 1 - break - } - c[0][i] = int((a << 3) | (a >> 2)) - c[1][i] = int((b << 3) | (b >> 2)) - } - } - - switch mode { - case 0: // individual & differential mode (ETC1) - codes[0] = modTbl0[opaque][(v64>>37)&7] - codes[1] = modTbl0[opaque][(v64>>34)&7] - - blockTbl := flipTbl[flip] - - i := uint(0) - for x := bx * 4; x < (bx+1)*4; x++ { - for y := by * 4; y < (by+1)*4; y++ { - if x < width && y < height { - block := blockTbl[i] - k := 4 * (y*width + x) - idx := ((v64 >> i) & 1) | ((v64 >> (15 + i)) & 2) - if opaque == 0 && idx == 2 { - dst[k+2] = 0 - dst[k+1] = 0 - dst[k+0] = 0 - dst[k+3] = 0 - } else { - base := c[block] - shift := codes[block][idx] - dst[k+2] = sint.Byte(base[2] + shift) - dst[k+1] = sint.Byte(base[1] + shift) - dst[k+0] = sint.Byte(base[0] + shift) - dst[k+3] = alpha[i] - } - } - i++ - } - } - case 1: // T-mode - // ┏━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┓ - // ┃ ┃ R₀ ┏━━┓ ┃ G₀ ┃ B₀ ┃ R₂ ┃ G₂ ┃ B₂ ┃ ┏━━┓ ┃ - // ┣━━┯━━┯━━╋━━┯━━╋━━╋━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━╋━━╋━━┫ - // ┃₆₃│₆₂│₆₁┃₆₀│₅₉┃₅₈┃₅₇│₅₆┃₅₅│₅₄│₅₃│₅₂┃₅₁│₅₀│₄₉│₄₈┃₄₇│₄₆│₄₅│₄₄┃₄₃│₄₂│₄₁│₄₀┃₃₉│₃₈│₃₇│₃₆┃₃₅│₃₄┃₃₃┃₃₂┃ - // ┖──┴──┴──┸──┴──┸──┸──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┸──┸──┚ - - // Load colors - c[0][R] = int(u64.Expand4to8(((v64 >> 57) & 12) | (v64>>56)&3)) - c[0][G] = int(u64.Expand4to8(v64 >> 52)) - c[0][B] = int(u64.Expand4to8(v64 >> 48)) - c[2][R] = int(u64.Expand4to8(v64 >> 44)) - c[2][G] = int(u64.Expand4to8(v64 >> 40)) - c[2][B] = int(u64.Expand4to8(v64 >> 36)) - - // Load intensity modifier - modIdx := ((v64 >> 33) & 6) | ((v64 >> 32) & 1) - mod := modTbl1[modIdx] - // Calculate C₁ and c₃ - for i := 0; i < 3; i++ { - c[1][i] = c[2][i] + mod - c[3][i] = c[2][i] - mod - } - // ┏━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┓ - // ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₃ ┃ x₃ ┃ x₃ ┃ x₃ ┃ - // ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ - // ┣━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━┫ - // ┃₃₁│₃₀┃₂₉│₂₈┃₂₇│₂₆┃₂₅│₂₄┃₂₃│₂₂┃₂₁│₂₀┃₁₉│₁₈┃₁₇│₁₆┃₁₅│₁₄┃₁₃│₁₂┃₁₁│₁₀┃ ₉│ ₈┃ ₇│ ₆┃ ₅│ ₄┃ ₃│ ₂┃ ₁│ ₀┃ - // ┖──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┚ - // Use 2-bit indices to lookup texel colors - i := uint(0) - for x := bx * 4; x < (bx+1)*4; x++ { - for y := by * 4; y < (by+1)*4; y++ { - if x < width && y < height { - k := 4 * (y*width + x) - idx := ((v64 >> i) & 1) | ((v64 >> (15 + i)) & 2) - if opaque == 0 && idx == 2 { - dst[k+0] = 0 - dst[k+1] = 0 - dst[k+2] = 0 - dst[k+3] = 0 - } else { - dst[k+0] = sint.Byte(c[idx][0]) - dst[k+1] = sint.Byte(c[idx][1]) - dst[k+2] = sint.Byte(c[idx][2]) - dst[k+3] = alpha[i] - } - } - i++ - } - } - case 2: // H-mode - // ┏━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━┓ - // ┃ ┃ R₀ ┃ G₀ ┏━━━━━━━━┓ ┃ ┏━━┓ B₀ ┃ R₂ ┃ G₂ ┃ B₂ ┃md┏━━┓ ┃ - // ┣━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━╋━━┯━━┯━━╋━━╋━━╋━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━╋━━╋━━╋━━┫ - // ┃₆₃┃₆₂│₆₁│₆₀│₅₉┃₅₈│₅₇│₅₆┃₅₅│₅₄│₅₃┃₅₂┃₅₁┃₅₀┃₄₉│₄₈│₄₇┃₄₆│₄₅│₄₄│₄₃┃₄₂│₄₁│₄₀│₃₉┃₃₈│₃₇│₃₆│₃₅┃₃₄┃₃₃┃₃₂┃ - // ┖──┸──┴──┴──┴──┸──┴──┴──┸──┴──┴──┸──┸──┸──┸──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┴──┴──┴──┸──┸──┸──┚ - - // Load colors - c[0][R] = int(u64.Expand4to8(v64 >> 59)) - c[0][G] = int(u64.Expand4to8(((v64 >> 55) & 14) | ((v64 >> 52) & 1))) - c[0][B] = int(u64.Expand4to8(((v64 >> 48) & 8) | ((v64 >> 47) & 7))) - c[2][R] = int(u64.Expand4to8(v64 >> 43)) - c[2][G] = int(u64.Expand4to8(v64 >> 39)) - c[2][B] = int(u64.Expand4to8(v64 >> 35)) - - // Load intensity modifier - modIdx := ((v64 >> 32) & 4) | ((v64 >> 31) & 2) - // LSB of modIdx is 1 if: - if (c[0][R]<<16)+(c[0][G]<<8)+c[0][B] >= (c[2][R]<<16)+(c[2][G]<<8)+c[2][B] { - modIdx++ - } - mod := modTbl1[modIdx] - // Calculate C₁ and c₃ - for i := 0; i < 3; i++ { - c[0][i], c[1][i] = c[0][i]+mod, c[0][i]-mod - c[2][i], c[3][i] = c[2][i]+mod, c[2][i]-mod - } - // ┏━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┳━━━━━┓ - // ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₀ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₁ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₂ ┃ x₃ ┃ x₃ ┃ x₃ ┃ x₃ ┃ - // ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ y₀ ┃ y₁ ┃ y₂ ┃ y₃ ┃ - // ┣━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━╋━━┯━━┫ - // ┃₃₁│₃₀┃₂₉│₂₈┃₂₇│₂₆┃₂₅│₂₄┃₂₃│₂₂┃₂₁│₂₀┃₁₉│₁₈┃₁₇│₁₆┃₁₅│₁₄┃₁₃│₁₂┃₁₁│₁₀┃ ₉│ ₈┃ ₇│ ₆┃ ₅│ ₄┃ ₃│ ₂┃ ₁│ ₀┃ - // ┖──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┸──┴──┚ - // Use 2-bit indices to lookup texel colors - i := uint(0) - for x := bx * 4; x < (bx+1)*4; x++ { - for y := by * 4; y < (by+1)*4; y++ { - if x < width && y < height { - k := 4 * (y*width + x) - idx := ((v64 >> i) & 1) | ((v64 >> (15 + i)) & 2) - if opaque == 0 && idx == 2 { - dst[k+0] = 0 - dst[k+1] = 0 - dst[k+2] = 0 - dst[k+3] = 0 - } else { - dst[k+0] = sint.Byte(c[idx][0]) - dst[k+1] = sint.Byte(c[idx][1]) - dst[k+2] = sint.Byte(c[idx][2]) - dst[k+3] = alpha[i] - } - } - i++ - } - } - case 3: // planar-mode - // ┏━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓ - // ┃ ┃ R₀ ┃ ┏━━┓ G₀ ┃ ┏━━━━━━━━┓ B₀ ┏━━┓ ┃ R₁ ┏━━┓ ┃ - // ┣━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━╋━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━╋━━┯━━┯━━╋━━┯━━╋━━╋━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━╋━━╋━━┫ - // ┃₆₃┃₆₂│₆₁│₆₀│₅₉│₅₈│₅₇┃₅₆┃₅₅┃₅₄│₅₃│₅₂│₅₁│₅₀│₄₉┃₄₈┃₄₇│₄₆│₄₅┃₄₄│₄₃┃₄₂┃₄₁│₄₀│₃₉┃₃₈│₃₇│₃₆│₃₅│₃₄┃₃₃┃₃₂┃ - // ┖──┸──┴──┴──┴──┴──┴──┸──┸──┸──┴──┴──┴──┴──┴──┸──┸──┴──┴──┸──┴──┸──┸──┴──┴──┸──┴──┴──┴──┴──┸──┸──┚ - // - // ┏━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓ - // ┃ G₁ ┃ B₁ ┃ R₂ ┃ G₂ ┃ B₂ ┃ - // ┣━━┯━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━┯━━╋━━┯━━┯━━┯━━┯━━┯━━┫ - // ┃₃₁│₃₀│₂₉│₂₈│₂₇│₂₆│₂₅┃₂₄│₂₃│₂₂│₂₁│₂₀│₁₉┃₁₈│₁₇│₁₆│₁₅│₁₄│₁₃┃₁₂│₁₁│₁₀│ ₉│ ₈│ ₇│ ₆┃ ₅│ ₄│ ₃│ ₂│ ₁│ ₀┃ - // ┖──┴──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┴──┸──┴──┴──┴──┴──┴──┚ - - // Load colors - c[0][R] = int(u64.Expand6to8(v64 >> 57)) - c[0][G] = int(u64.Expand7to8(((v64 >> 50) & 64) | ((v64 >> 49) & 63))) - c[0][B] = int(u64.Expand6to8(((v64 >> 43) & 32) | ((v64 >> 40) & 24) | ((v64 >> 39) & 7))) - - c[1][R] = int(u64.Expand6to8(((v64 >> 33) & 62) | ((v64 >> 32) & 1))) - c[1][G] = int(u64.Expand7to8(v64 >> 25)) - c[1][B] = int(u64.Expand6to8(v64 >> 19)) - - c[2][R] = int(u64.Expand6to8(v64 >> 13)) - c[2][G] = int(u64.Expand7to8(v64 >> 6)) - c[2][B] = int(u64.Expand6to8(v64)) - - i := 0 - for dx := 0; dx < 4; dx++ { - x := bx*4 + dx - for dy := 0; dy < 4; dy++ { - y := by*4 + dy - if x < width && y < height { - k := 4 * (y*width + x) - dst[k+0] = sint.Byte((dx*(c[1][R]-c[0][R]) + dy*(c[2][R]-c[0][R]) + 4*c[0][R] + 2) >> 2) - dst[k+1] = sint.Byte((dx*(c[1][G]-c[0][G]) + dy*(c[2][G]-c[0][G]) + 4*c[0][G] + 2) >> 2) - dst[k+2] = sint.Byte((dx*(c[1][B]-c[0][B]) + dy*(c[2][B]-c[0][B]) + 4*c[0][B] + 2) >> 2) - dst[k+3] = alpha[i] - } - i++ - } - } - } - } + if f.ColorMode == FmtETC2_RGB || f.ColorMode == FmtETC2_SRGB { + if f.AlphaMode == FmtETC2_ALPHA_NONE { + return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue} } - } - - return dst, nil -} - -func decodeETCU11(src []byte, width, height, depth int, channels int) ([]byte, error) { - dst := make([]byte, width*height*depth*channels*2) - blockWidth := sint.Max((width+3)/4, 1) - blockHeight := sint.Max((height+3)/4, 1) - r := endian.Reader(bytes.NewReader(src), device.BigEndian) - for z := 0; z < depth; z++ { - dst := dst[z*width*height*channels*2:] - for by := 0; by < blockHeight; by++ { - for bx := 0; bx < blockWidth; bx++ { - for c := 0; c < channels; c++ { - v64 := r.Uint64() - base, mul, modTbl := decodeETCBaseMulModTbl(v64) - if mul != 0 { - mul *= 8 - } else { - mul = 1 - } - i := uint(15) - for x := bx * 4; x < bx*4+4; x++ { - for y := by * 4; y < by*4+4; y++ { - if x < width && y < height { - mod := modTbl[(v64>>(i*3))&7] - u11 := uint(sint.Clamp(base*8+4+mod*mul, 0, 2047)) - u16 := (u11 << 5) | (u11 >> 5) - k := 2*channels*(y*width+x) + c*2 - dst[k+0] = byte(u16) - dst[k+1] = byte(u16 >> 8) - } - i-- - } - } - } - } - } - } - - return dst, nil -} - -func decodeETCS11(src []byte, width, height, depth int, channels int) ([]byte, error) { - dst := make([]byte, width*height*depth*channels*2) - blockWidth := sint.Max((width+3)/4, 1) - blockHeight := sint.Max((height+3)/4, 1) - r := endian.Reader(bytes.NewReader(src), device.BigEndian) - for z := 0; z < depth; z++ { - dst := dst[z*width*height*channels*2:] - for by := 0; by < blockHeight; by++ { - for bx := 0; bx < blockWidth; bx++ { - for c := 0; c < channels; c++ { - v64 := r.Uint64() - base, mul, modTbl := decodeETCBaseMulModTbl(v64) - base = int(int8(base)) - if mul != 0 { - mul *= 8 - } else { - mul = 1 - } - i := uint(15) - for x := bx * 4; x < bx*4+4; x++ { - for y := by * 4; y < by*4+4; y++ { - if x < width && y < height { - mod := modTbl[(v64>>(i*3))&7] - s11 := sint.Clamp(base*8+mod*mul, -1023, 1023) - var s16 int - if s11 >= 0 { - s16 = (s11 << 5) | (s11 >> 5) - } else { - s16 = -((-s11 << 5) | (-s11 >> 5)) - } - k := 2*channels*(y*width+x) + c*2 - dst[k+0] = byte(s16) - dst[k+1] = byte(s16 >> 8) - } - i-- - } - } - } - } - } + return stream.Channels{stream.Channel_Red, stream.Channel_Green, stream.Channel_Blue, stream.Channel_Alpha} } - return dst, nil + panic("Unknown ETC color format") } diff --git a/core/image/format.go b/core/image/format.go index e1dddaeb41..638a5b76fd 100644 --- a/core/image/format.go +++ b/core/image/format.go @@ -57,13 +57,7 @@ var _ = []format{ &FmtATC_RGBA_EXPLICIT_ALPHA_AMD{}, &FmtATC_RGBA_INTERPOLATED_ALPHA_AMD{}, &FmtETC1_RGB_U8_NORM{}, - &FmtETC2_RGB_U8_NORM{}, - &FmtETC2_RGBA_U8_NORM{}, - &FmtETC2_RGBA_U8U8U8U1_NORM{}, - &FmtETC2_R_U11_NORM{}, - &FmtETC2_RG_U11_NORM{}, - &FmtETC2_R_S11_NORM{}, - &FmtETC2_RG_S11_NORM{}, + &FmtETC2{}, &FmtRGTC1_BC4_R_U8_NORM{}, &FmtRGTC1_BC4_R_S8_NORM{}, &FmtRGTC2_BC5_RG_U8_NORM{}, diff --git a/core/image/image.go b/core/image/image.go index 0d8a0bd7cf..4734327a03 100644 --- a/core/image/image.go +++ b/core/image/image.go @@ -47,11 +47,12 @@ func (i *Info) Convert(ctx context.Context, f *Format) (*Info, error) { return nil, fmt.Errorf("Failed to convert ImageInfo to format %v: %v", f, err) } return &Info{ - Format: f, - Width: i.Width, - Height: i.Height, - Depth: i.Depth, - Bytes: NewID(id), + Format: f, + Width: i.Width, + Height: i.Height, + Depth: i.Depth, + Bytes: NewID(id), + ComputedSize: uint32(f.Size(int(i.Width), int(i.Height), int(i.Depth))), }, nil } @@ -71,11 +72,12 @@ func (i *Info) Resize(ctx context.Context, w, h, d uint32) (*Info, error) { return nil, fmt.Errorf("Failed to resize ImageInfo to %v x %v: %v", w, h, err) } return &Info{ - Format: i.Format, - Width: w, - Height: h, - Depth: d, - Bytes: NewID(id), + Format: i.Format, + Width: w, + Height: h, + Depth: d, + Bytes: NewID(id), + ComputedSize: uint32(i.Format.Size(int(w), int(h), int(d))), }, nil } @@ -111,11 +113,12 @@ func (b *Data) NewInfo(ctx context.Context) (*Info, error) { return nil, fmt.Errorf("Failed to create imageInfo from imageData %v: %v", b, err) } return &Info{ - Format: b.Format, - Width: b.Width, - Height: b.Height, - Depth: b.Depth, - Bytes: NewID(id), + Format: b.Format, + Width: b.Width, + Height: b.Height, + Depth: b.Depth, + Bytes: NewID(id), + ComputedSize: uint32(len(b.Bytes)), }, nil } diff --git a/core/image/image.proto b/core/image/image.proto index c4339cca13..81577c80fc 100644 --- a/core/image/image.proto +++ b/core/image/image.proto @@ -52,6 +52,8 @@ message Info { uint32 depth = 4; // The identifier of the image data as bytes. ID bytes = 5; + // The computed size of the image. + uint32 computed_size = 6; } message Format { @@ -63,22 +65,16 @@ message Format { FmtATC_RGBA_EXPLICIT_ALPHA_AMD atc_rgba_explicit_alpha_amd = 5; FmtATC_RGBA_INTERPOLATED_ALPHA_AMD atc_rgba_interpolated_alpha_amd = 6; FmtETC1_RGB_U8_NORM etc1_rgb_u8_norm = 7; - FmtETC2_RGB_U8_NORM etc2_rgb_u8_norm = 8; - FmtETC2_RGBA_U8_NORM etc2_rgba_u8_norm = 9; - FmtETC2_RGBA_U8U8U8U1_NORM etc2_rgba_u8u8u8u1_norm = 10; - FmtETC2_R_U11_NORM etc2_r_u11_norm = 11; - FmtETC2_RG_U11_NORM etc2_rg_u11_norm = 12; - FmtETC2_R_S11_NORM etc2_r_s11_norm = 13; - FmtETC2_RG_S11_NORM etc2_rg_s11_norm = 14; - FmtS3_DXT1_RGB s3_dxt1_rgb = 15; - FmtS3_DXT1_RGBA s3_dxt1_rgba = 16; - FmtS3_DXT3_RGBA s3_dxt3_rgba = 17; - FmtS3_DXT5_RGBA s3_dxt5_rgba = 18; - FmtASTC astc = 19; - FmtRGTC1_BC4_R_U8_NORM rgtc1_bc4_r_u8_norm = 20; - FmtRGTC1_BC4_R_S8_NORM rgtc1_bc4_r_s8_norm = 21; - FmtRGTC2_BC5_RG_U8_NORM rgtc2_bc5_rg_u8_norm = 22; - FmtRGTC2_BC5_RG_S8_NORM rgtc2_bc5_rg_s8_norm = 23; + FmtETC2 etc2 = 8; + FmtS3_DXT1_RGB s3_dxt1_rgb = 9; + FmtS3_DXT1_RGBA s3_dxt1_rgba = 10; + FmtS3_DXT3_RGBA s3_dxt3_rgba = 11; + FmtS3_DXT5_RGBA s3_dxt5_rgba = 12; + FmtASTC astc = 13; + FmtRGTC1_BC4_R_U8_NORM rgtc1_bc4_r_u8_norm = 14; + FmtRGTC1_BC4_R_S8_NORM rgtc1_bc4_r_s8_norm = 15; + FmtRGTC2_BC5_RG_U8_NORM rgtc2_bc5_rg_u8_norm = 16; + FmtRGTC2_BC5_RG_S8_NORM rgtc2_bc5_rg_s8_norm = 17; } } @@ -93,25 +89,30 @@ message FmtATC_RGBA_EXPLICIT_ALPHA_AMD { } message FmtATC_RGBA_INTERPOLATED_ALPHA_AMD { } + message FmtETC1_RGB_U8_NORM { } -message FmtETC2_RGB_U8_NORM { - bool srgb = 1; -} -message FmtETC2_RGBA_U8_NORM { - bool srgb = 1; -} -message FmtETC2_RGBA_U8U8U8U1_NORM { - bool srgb = 1; -} -message FmtETC2_R_U11_NORM { -} -message FmtETC2_RG_U11_NORM { -} -message FmtETC2_R_S11_NORM { -} -message FmtETC2_RG_S11_NORM { + +message FmtETC2 { + enum ColorMode { + R = 0; + R_SIGNED = 1; + RG = 2; + RG_SIGNED = 3; + RGB = 4; + SRGB = 5; + } + + enum AlphaMode { + ALPHA_NONE = 0; + ALPHA_8BIT = 1; + ALPHA_1BIT = 2; + } + + ColorMode colorMode = 1; + AlphaMode alphaMode = 2; } + message FmtS3_DXT1_RGB { } message FmtS3_DXT1_RGBA { diff --git a/core/image/test_data/.DS_Store b/core/image/test_data/.DS_Store deleted file mode 100644 index 5008ddfcf5..0000000000 Binary files a/core/image/test_data/.DS_Store and /dev/null differ diff --git a/core/image/uncompressed.go b/core/image/uncompressed.go index c713bfd6a8..798d07c752 100644 --- a/core/image/uncompressed.go +++ b/core/image/uncompressed.go @@ -34,6 +34,10 @@ var ( RG_S16_NORM = newUncompressed(fmts.RG_S16_NORM) Gray_U8_NORM = newUncompressed(fmts.Gray_U8_NORM) D_U16_NORM = newUncompressed(fmts.D_U16_NORM) + + Luminance_R32 = newUncompressed(fmts.L_F32) + Luminance_U8_NORM = newUncompressed(fmts.L_U8_NORM) + LuminanceA_U8_NORM = newUncompressed(fmts.LA_U8_NORM) ) // newUncompressed returns a new uncompressed format containing with the default @@ -66,7 +70,7 @@ func (f *FmtUncompressed) channels() stream.Channels { } func (f *FmtUncompressed) resize(data []byte, srcW, srcH, srcD, dstW, dstH, dstD int) ([]byte, error) { - format := &Format{Name: "", Format: &Format_Uncompressed{f}} + format := &Format{Name: "Temporary Uncompressed Format", Format: &Format_Uncompressed{f}} data, err := Convert(data, srcW, srcH, srcD, format, RGBA_F32) if err != nil { return nil, err diff --git a/core/java/jdbg/BUILD.bazel b/core/java/jdbg/BUILD.bazel deleted file mode 100644 index f61135e886..0000000000 --- a/core/java/jdbg/BUILD.bazel +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "assignable.go", - "errors.go", - "failure.go", - "jdbg.go", - "marshal.go", - "method_signature.go", - "methods.go", - "resolve_overload.go", - "signature.go", - "type.go", - "value.go", - ], - importpath = "github.com/google/gapid/core/java/jdbg", - visibility = ["//visibility:public"], - deps = ["//core/java/jdwp:go_default_library"], -) - -go_test( - name = "go_default_test", - size = "small", - srcs = ["jdbg_test.go"], - embed = [":go_default_library"], - tags = ["integration"], - deps = [ - "//core/assert:go_default_library", - "//core/java/jdwp:go_default_library", - "//core/java/jdwp/test:go_default_library", - "//core/log:go_default_library", - ], -) diff --git a/core/java/jdbg/assignable.go b/core/java/jdbg/assignable.go deleted file mode 100644 index 8f18a27738..0000000000 --- a/core/java/jdbg/assignable.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "fmt" - "reflect" -) - -// assignable returns true if the type of val can be assigned to dst. -func (j *JDbg) assignable(dst Type, val interface{}) bool { - if v, ok := val.(Value); ok { - if v.ty.CastableTo(dst) { - return true - } - if dst.Signature() == v.ty.Signature() { - // Sanity check. - panic(fmt.Sprint("Two different types found with identical signatures! ", dst.Signature())) - } - return j.assignable(dst, v.val) - } - return j.assignableT(dst, reflect.TypeOf(val)) -} - -func (j *JDbg) assignableT(dst Type, src reflect.Type) bool { - if dst == j.cache.objTy { - return true // Anything goes in an object! - } - if src == nil { - _, isClass := dst.(*Class) - return isClass - } - switch src.Kind() { - case reflect.Ptr, reflect.Interface: - return j.assignableT(dst, src.Elem()) - case reflect.Bool: - return dst == j.cache.boolTy - case reflect.Int8, reflect.Uint8: - return dst == j.cache.byteTy - case reflect.Int16: - return dst == j.cache.charTy || dst == j.cache.shortTy - case reflect.Int32, reflect.Int: - return dst == j.cache.intTy - case reflect.Int64: - return dst == j.cache.longTy - case reflect.Float32: - return dst == j.cache.floatTy - case reflect.Float64: - return dst == j.cache.doubleTy - case reflect.String: - return dst == j.cache.stringTy - case reflect.Array, reflect.Slice: - if dstArray, ok := dst.(*Array); ok { - return j.assignableT(dstArray.el, src.Elem()) - } - } - - return false -} diff --git a/core/java/jdbg/errors.go b/core/java/jdbg/errors.go deleted file mode 100644 index 11f9c78156..0000000000 --- a/core/java/jdbg/errors.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "fmt" - "strings" -) - -// Errors is a collection of errors. -type Errors []error - -func (l Errors) Error() string { - lines := make([]string, len(l)) - for i, e := range l { - lines[i] = e.Error() - } - return fmt.Sprintf("%d errors:\n%v", len(lines), strings.Join(lines, "\n")) -} diff --git a/core/java/jdbg/failure.go b/core/java/jdbg/failure.go deleted file mode 100644 index 0b50b42959..0000000000 --- a/core/java/jdbg/failure.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import "fmt" - -// failure is the error type that can be thrown as a panic by JDbg.fail() and -// JDbg.err() and is caught by catchFailures(). It is used to immediately -// terminate execution of Do(), and return the error. -type failure struct { - error -} - -// fail panics with a failure error formed from msg and args, immediately -// terminating execution of Do(). -func (j *JDbg) fail(msg string, args ...interface{}) { - j.err(fmt.Errorf(msg, args...)) -} - -// err panics with a failure error holding err, immediately terminating -// execution of Do(). -func (j *JDbg) err(err error) { - panic(failure{err}) -} - -// Try calls f, catching and returning any exceptions thrown. -func Try(f func() error) (err error) { - defer func() { - switch r := recover().(type) { - case nil: - case failure: - err = r - default: - panic(r) - } - }() - err = f() - return err -} diff --git a/core/java/jdbg/jdbg.go b/core/java/jdbg/jdbg.go deleted file mode 100644 index 21e509be8b..0000000000 --- a/core/java/jdbg/jdbg.go +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// Package jdbg provides a simpler interface to the jdwp package, offering -// simple type resolving and method invoking functions. -package jdbg - -import ( - "fmt" - "strings" - - "github.com/google/gapid/core/java/jdwp" -) - -type cache struct { - arrays map[string]*Array - classes map[string]*Class - idToSig map[jdwp.ReferenceTypeID]string - - objTy *Class - stringTy *Class - numberTy *Class - boolObjTy *Class - byteObjTy *Class - charObjTy *Class - shortObjTy *Class - intObjTy *Class - longObjTy *Class - floatObjTy *Class - doubleObjTy *Class - boolTy *Simple - byteTy *Simple - charTy *Simple - shortTy *Simple - intTy *Simple - longTy *Simple - floatTy *Simple - doubleTy *Simple - - voidTy *Simple -} - -// JDbg is a wrapper around a JDWP connection that provides an easier interface -// for usage. -type JDbg struct { - conn *jdwp.Connection - thread jdwp.ThreadID - cache cache - objects []jdwp.ObjectID // Objects created that have GC disabled -} - -// Do calls f with a JDbg instance, returning the error returned by f. -// If any JDWP errors are raised during the call to f, then execution of f is -// immediately terminated, and the JDWP error is returned. -func Do(conn *jdwp.Connection, thread jdwp.ThreadID, f func(jdbg *JDbg) error) error { - j := &JDbg{ - conn: conn, - thread: thread, - cache: cache{ - arrays: map[string]*Array{}, - classes: map[string]*Class{}, - idToSig: map[jdwp.ReferenceTypeID]string{}, - }, - } - defer func() { - // Reenable GC for all objects used during the call to f() - for _, o := range j.objects { - conn.EnableGC(o) - } - }() - - return Try(func() error { - // Prime the cache with basic types. - j.cache.voidTy = &Simple{j: j, ty: jdwp.TagVoid} - j.cache.boolTy = &Simple{j: j, ty: jdwp.TagBoolean} - j.cache.byteTy = &Simple{j: j, ty: jdwp.TagByte} - j.cache.charTy = &Simple{j: j, ty: jdwp.TagChar} - j.cache.shortTy = &Simple{j: j, ty: jdwp.TagShort} - j.cache.intTy = &Simple{j: j, ty: jdwp.TagInt} - j.cache.longTy = &Simple{j: j, ty: jdwp.TagLong} - j.cache.floatTy = &Simple{j: j, ty: jdwp.TagFloat} - j.cache.doubleTy = &Simple{j: j, ty: jdwp.TagDouble} - j.cache.objTy = j.Class("java.lang.Object") - j.cache.stringTy = j.Class("java.lang.String") - j.cache.numberTy = j.Class("java.lang.Number") - j.cache.boolObjTy = j.Class("java.lang.Boolean") - j.cache.byteObjTy = j.Class("java.lang.Byte") - j.cache.charObjTy = j.Class("java.lang.Character") - j.cache.shortObjTy = j.Class("java.lang.Short") - j.cache.intObjTy = j.Class("java.lang.Integer") - j.cache.longObjTy = j.Class("java.lang.Long") - j.cache.floatObjTy = j.Class("java.lang.Float") - j.cache.doubleObjTy = j.Class("java.lang.Double") - - // Call f - return f(j) - }) -} - -// Connection returns the JDWP connection. -func (j *JDbg) Connection() *jdwp.Connection { return j.conn } - -// ObjectType returns the Java java.lang.Object type. -func (j *JDbg) ObjectType() *Class { return j.cache.objTy } - -// StringType returns the Java java.lang.String type. -func (j *JDbg) StringType() *Class { return j.cache.stringTy } - -// NumberType returns the Java java.lang.Number type. -func (j *JDbg) NumberType() *Class { return j.cache.numberTy } - -// BoolObjectType returns the Java java.lang.Boolean type. -func (j *JDbg) BoolObjectType() *Class { return j.cache.boolObjTy } - -// ByteObjectType returns the Java java.lang.Byte type. -func (j *JDbg) ByteObjectType() *Class { return j.cache.byteObjTy } - -// CharObjectType returns the Java java.lang.Character type. -func (j *JDbg) CharObjectType() *Class { return j.cache.charObjTy } - -// ShortObjectType returns the Java java.lang.Short type. -func (j *JDbg) ShortObjectType() *Class { return j.cache.shortObjTy } - -// IntObjectType returns the Java java.lang.Integer type. -func (j *JDbg) IntObjectType() *Class { return j.cache.intObjTy } - -// LongObjectType returns the Java java.lang.Long type. -func (j *JDbg) LongObjectType() *Class { return j.cache.longObjTy } - -// FloatObjectType returns the Java java.lang.Float type. -func (j *JDbg) FloatObjectType() *Class { return j.cache.floatObjTy } - -// DoubleObjectType returns the Java java.lang.Double type. -func (j *JDbg) DoubleObjectType() *Class { return j.cache.doubleObjTy } - -// BoolType returns the Java java.lang.Boolean type. -func (j *JDbg) BoolType() *Simple { return j.cache.boolTy } - -// ByteType returns the Java byte type. -func (j *JDbg) ByteType() *Simple { return j.cache.byteTy } - -// CharType returns the Java char type. -func (j *JDbg) CharType() *Simple { return j.cache.charTy } - -// ShortType returns the Java short type. -func (j *JDbg) ShortType() *Simple { return j.cache.shortTy } - -// IntType returns the Java int type. -func (j *JDbg) IntType() *Simple { return j.cache.intTy } - -// LongType returns the Java long type. -func (j *JDbg) LongType() *Simple { return j.cache.longTy } - -// FloatType returns the Java float type. -func (j *JDbg) FloatType() *Simple { return j.cache.floatTy } - -// DoubleType returns the Java double type. -func (j *JDbg) DoubleType() *Simple { return j.cache.doubleTy } - -// Type looks up the specified type by signature. -// For example: "Ljava/io/File;" -func (j *JDbg) Type(sig string) Type { - offset := 0 - ty, err := j.parseSignature(sig, &offset) - if err != nil { - j.fail("Failed to parse signature: %v", err) - } - return ty -} - -// Class looks up the specified class by name. -// For example: "java.io.File" -func (j *JDbg) Class(name string) *Class { - ty := j.Type(fmt.Sprintf("L%s;", strings.Replace(name, ".", "/", -1))) - if class, ok := ty.(*Class); ok { - return class - } - j.fail("Resolved type was not array but %T", ty) - return nil -} - -// AllClasses returns all the loaded classes. -func (j *JDbg) AllClasses() []*Class { - classes, err := j.conn.GetAllClasses() - if err != nil { - j.fail("Couldn't get all classes: %v", err) - } - out := []*Class{} - for _, class := range classes { - c, err := j.class(class) - if err != nil { - j.fail("Couldn't get class '%v': %v", class.Signature, err) - } - out = append(out, c) - } - return out -} - -// ArrayOf returns the type of the array with specified element type. -func (j *JDbg) ArrayOf(elTy Type) *Array { - ty := j.Type("[" + elTy.Signature()) - if array, ok := ty.(*Array); ok { - return array - } - j.fail("Resolved type was not array but %T", ty) - return nil -} - -// classFromSig looks up the specified class type by signature. -func (j *JDbg) classFromSig(sig string) (*Class, error) { - if class, ok := j.cache.classes[sig]; ok { - return class, nil - } - class, err := j.conn.GetClassBySignature(sig) - if err != nil { - return nil, err - } - return j.class(class) -} - -func (j *JDbg) class(class jdwp.ClassInfo) (*Class, error) { - sig := class.Signature - if cached, ok := j.cache.classes[class.Signature]; ok { - return cached, nil - } - - name := strings.Replace(strings.TrimRight(strings.TrimLeft(sig, "[L"), ";"), "/", ".", -1) - - ty := &Class{j: j, signature: sig, name: name, class: class} - j.cache.classes[sig] = ty - j.cache.idToSig[class.TypeID] = sig - - superid, err := j.conn.GetSuperClass(class.ClassID()) - if err != nil { - return nil, err - } - - if superid != 0 { - ty.super = j.typeFromID(jdwp.ReferenceTypeID(superid)).(*Class) - } - - implementsids, err := j.conn.GetImplemented(class.TypeID) - if err != nil { - return nil, err - } - - ty.implements = make([]*Class, len(implementsids)) - for i, id := range implementsids { - ty.implements[i] = j.typeFromID(jdwp.ReferenceTypeID(id)).(*Class) - } - - ty.fields, err = j.conn.GetFields(class.TypeID) - if err != nil { - return nil, err - } - - return ty, nil -} - -func (j *JDbg) typeFromID(id jdwp.ReferenceTypeID) Type { - sig, ok := j.cache.idToSig[id] - if !ok { - var err error - sig, err = j.conn.GetTypeSignature(id) - if err != nil { - j.fail("GetTypeSignature() returned: %v", err) - } - j.cache.idToSig[id] = sig - } - return j.Type(sig) -} - -// This returns the this object for the current stack frame. -func (j *JDbg) This() Value { - frames, err := j.conn.GetFrames(j.thread, 0, 1) - if err != nil { - j.fail("GetFrames() returned: %v", err) - } - - this, err := j.conn.GetThisObject(j.thread, frames[0].Frame) - if err != nil { - j.fail("GetThisObject() returned: %v", err) - } - - return j.object(this.Object) -} - -func (j *JDbg) String(val string) Value { - str, err := j.conn.CreateString(val) - if err != nil { - j.fail("CreateString() returned: %v", err) - } - return j.object(str) -} - -// findArg finds the argument with the given name/index in the given frame -func (j *JDbg) findArg(name string, index int, frame jdwp.FrameInfo) jdwp.VariableRequest { - table, err := j.conn.VariableTable( - jdwp.ReferenceTypeID(frame.Location.Class), - frame.Location.Method) - - if err != nil { - j.fail("VariableTable returned: %v", err) - } - - variable := jdwp.VariableRequest{-1, 0} - - for _, slot := range table.Slots { - if name == slot.Name { - variable.Index = slot.Slot - variable.Tag = slot.Signature[0] - } - } - - if variable.Index != -1 { - return variable - } - - // Fallback to looking for the argument by index. - slots := table.ArgumentSlots() - - // Find the "this" argument. It is always labeled and the first argument slot. - thisSlot := -1 - for i, slot := range slots { - if slot.Name == "this" { - thisSlot = i - break - } - } - if thisSlot < 0 { - j.fail("Could not find argument with name %s (no 'this' found)", name) - } - - if thisSlot+1+index >= len(slots) { - j.fail("Could not find argument with name %s (not enough slots)", name) - } - - variable.Index = slots[thisSlot+1+index].Slot - variable.Tag = slots[thisSlot+1+index].Signature[0] - return variable -} - -// GetArgument returns the method argument of the given name and index. First, -// this attempts to retrieve the argument by name, but falls back to looking for -// the argument by index (e.g. in the case the names have been stripped from the -// debug info). -func (j *JDbg) GetArgument(name string, index int) Variable { - frames, err := j.conn.GetFrames(j.thread, 0, 1) - if err != nil { - j.fail("GetFrames() returned: %v", err) - } - variable := j.findArg(name, index, frames[0]) - - values, err := j.conn.GetValues(j.thread, frames[0].Frame, []jdwp.VariableRequest{variable}) - if err != nil { - j.fail("GetValues() returned: %v", err) - } - return Variable{j.value(values[0]), variable} -} - -// SetVariable sets the value of the given variable. -func (j *JDbg) SetVariable(variable Variable, val Value) { - frames, err := j.conn.GetFrames(j.thread, 0, 1) - if err != nil { - j.fail("GetFrames() returned: %v", err) - } - - v := val.val.(jdwp.Value) - assign := jdwp.VariableAssignmentRequest{variable.variable.Index, v} - err = j.conn.SetValues(j.thread, frames[0].Frame, []jdwp.VariableAssignmentRequest{assign}) - if err != nil { - j.fail("GetValues() returned: %v", err) - } -} - -func (j *JDbg) object(id jdwp.Object) Value { - tyID, err := j.conn.GetObjectType(id.ID()) - if err != nil { - j.fail("GetObjectType() returned: %v", err) - } - - ty := j.typeFromID(tyID.Type) - return newValue(ty, id) -} - -func (j *JDbg) value(o interface{}) Value { - switch v := o.(type) { - case jdwp.Object: - return j.object(v) - default: - j.fail("Unhandled variable type %T", o) - return Value{} - } -} diff --git a/core/java/jdbg/jdbg_test.go b/core/java/jdbg/jdbg_test.go deleted file mode 100644 index 95918e3fb6..0000000000 --- a/core/java/jdbg/jdbg_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg_test - -import ( - "context" - "os" - "testing" - - "github.com/google/gapid/core/assert" - "github.com/google/gapid/core/java/jdbg" - "github.com/google/gapid/core/java/jdwp" - "github.com/google/gapid/core/java/jdwp/test" - "github.com/google/gapid/core/log" -) - -var source = map[string]string{ - "main.java": ` -public class main { - static { - Calculator.register(); - } - public static void main(String[] args) throws Exception { - System.out.print("Doing stuff\n"); - Thread.sleep(1000); - } -}`, - "Calculator.java": ` -public class Calculator { - public static void register() {} - - private int value = 0; - - public static int Add(int a, int b) { return a + b; } - public void Add(int a) { value += a; } - - public int Result() { return value; } -} -`, -} - -func TestMain(m *testing.M) { - ctx := context.Background() - ctx = log.PutHandler(ctx, log.Normal.Handler(log.Std())) - os.Exit(test.BuildRunAndConnect(ctx, source, func(ctx context.Context, c *jdwp.Connection) int { - if err := c.ResumeAll(); err != nil { - log.F(ctx, true, "Failed resume VM. Error: %v", err) - return -1 - } - - // Wait for java to load the prepare class - t, err := c.WaitForClassPrepare(ctx, "Calculator") - if err != nil { - log.F(ctx, true, "Failed to wait for Calculator prepare. Error: %v", err) - return -1 - } - - conn, thread = c, t - return m.Run() - })) -} - -var conn *jdwp.Connection -var thread jdwp.ThreadID - -func TestInvokeStringFormat(t *testing.T) { - assert := assert.To(t) - err := jdbg.Do(conn, thread, func(j *jdbg.JDbg) error { - res := j.Class("java/lang/String").Call("format", "%s says '%s' %d times", - []interface{}{"bob", "hello world", 5}).Get() - assert.For("res").That(res).Equals("bob says 'hello world' 5 times") - return nil - }) - assert.For("err").That(err).Equals(nil) -} - -func TestInvokeStaticMethod(t *testing.T) { - assert := assert.To(t) - err := jdbg.Do(conn, thread, func(j *jdbg.JDbg) error { - res := j.Class("Calculator").Call("Add", 3, 7).Get() - assert.For("res").That(res).Equals(10) - return nil - }) - assert.For("err").That(err).Equals(nil) -} - -func TestInvokeMethod(t *testing.T) { - assert := assert.To(t) - err := jdbg.Do(conn, thread, func(j *jdbg.JDbg) error { - calcTy := j.Class("Calculator") - - calc := calcTy.New() - for _, i := range []int{3, 6, 8} { - calc.Call("Add", i) - } - - res := calc.Call("Result").Get() - assert.For("res").That(res).Equals(3 + 6 + 8) - return nil - }) - assert.For("err").That(err).Equals(nil) - -} diff --git a/core/java/jdbg/marshal.go b/core/java/jdbg/marshal.go deleted file mode 100644 index a847c14dea..0000000000 --- a/core/java/jdbg/marshal.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "reflect" - - "github.com/google/gapid/core/java/jdwp" -) - -// marshal transforms o into a jdwp.Value. -func (j *JDbg) marshal(o interface{}) jdwp.Value { - switch o := o.(type) { - case bool, jdwp.Char, int, int8, - int16, int32, int64, float32, float64: - return o - - case nil: - return jdwp.ObjectID(0) - - case jdwp.Object: - return o.ID() - - case Value: - return j.marshal(o.val) - - case string: - id, err := j.conn.CreateString(o) - if err != nil { - j.fail("Failed to marshal string: %v", err) - } - return id - - case []byte: - return j.newArray(j.cache.byteTy, o) - - case []interface{}: - return j.newArray(j.cache.objTy, o) - - default: - j.fail("Unhandled type %T", o) - return nil - } -} - -// newArray creates a new array with element type elTy, filled with values. -func (j *JDbg) newArray(elTy Type, values interface{}) jdwp.ArrayID { - array := j.ArrayOf(elTy).New(reflect.ValueOf(values).Len()) - array.SetArrayValues(values) - return array.val.(jdwp.ArrayID) -} - -// toObjectType returns the corresponding java.lang.Object type for ty, or nil -// if there is no corresponding object type. -func (j *JDbg) toObjectType(ty reflect.Type) *Class { - switch ty.Kind() { - case reflect.Int: - return j.cache.intObjTy - case reflect.Int8, reflect.Uint8: - return j.cache.byteObjTy - case reflect.Int16: - return j.cache.shortObjTy - case reflect.Int32: - return j.cache.intObjTy - case reflect.Int64: - return j.cache.longObjTy - case reflect.Float32: - return j.cache.floatObjTy - case reflect.Float64: - return j.cache.doubleObjTy - default: - return nil - } -} - -// toObject returns the value of o transformed to a java.lang.Object held in a -// jdwp.Value. -func (j *JDbg) toObject(o interface{}) jdwp.Value { - if obj := j.toObjectType(reflect.TypeOf(o)); obj != nil { - return j.marshal(obj.New(o)) - } - - switch o := j.marshal(o).(type) { - case jdwp.ObjectID, jdwp.StringID, jdwp.ArrayID: - return o - case jdwp.TaggedObjectID: - return o.Object - default: - j.fail("Cannot convert %v (%T) to Object", o, o) - return nil - } -} - -func (j *JDbg) toObjects(l []interface{}) []interface{} { - objects := make([]interface{}, len(l)) - for i, v := range l { - objects[i] = j.toObject(v) - } - return objects -} - -// unmarshal unboxes the jdwp.Value into a corresponding golang value. -func (j *JDbg) unmarshal(v jdwp.Value) interface{} { - switch v := v.(type) { - case jdwp.StringID: - str, err := j.conn.GetString(v) - if err != nil { - j.fail("Failed to unmarshal string") - } - return str - default: - return v - } -} - -func (j *JDbg) marshalN(v []interface{}) []jdwp.Value { - out := make([]jdwp.Value, len(v)) - for i, v := range v { - out[i] = j.marshal(v) - } - return out -} - -func (j *JDbg) unmarshalN(v []jdwp.Value) []interface{} { - out := make([]interface{}, len(v)) - for i, v := range v { - out[i] = j.unmarshal(v) - } - return out -} diff --git a/core/java/jdbg/method_signature.go b/core/java/jdbg/method_signature.go deleted file mode 100644 index 20000469c7..0000000000 --- a/core/java/jdbg/method_signature.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import "fmt" - -// methodSignature represents a method signature -type methodSignature struct { - Parameters []Type - Return Type -} - -// accepts returns true if the signature accepts the list of arguments. -func (j *JDbg) accepts(s methodSignature, args []interface{}) bool { - if len(args) != len(s.Parameters) { - return false - } - for i := range args { - if !j.assignable(s.Parameters[i], args[i]) { - return false - } - } - return true -} - -// parseMethodSignature returns the signature from the string str. -func (j *JDbg) parseMethodSignature(str string) (methodSignature, error) { - s := methodSignature{} - if str[0] != '(' { - return methodSignature{}, fmt.Errorf("Method signature doesn't start with '('") - } - i := 1 - for str[i] != ')' { - ty, err := j.parseSignature(str, &i) - if err != nil { - return methodSignature{}, err - } - s.Parameters = append(s.Parameters, ty) - } - i++ - ty, err := j.parseSignature(str, &i) - if err != nil { - return methodSignature{}, err - } - s.Return = ty - return s, nil -} diff --git a/core/java/jdbg/methods.go b/core/java/jdbg/methods.go deleted file mode 100644 index 24a24b37d5..0000000000 --- a/core/java/jdbg/methods.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "fmt" - "strings" - - "github.com/google/gapid/core/java/jdwp" -) - -const ( - constructor = "" -) - -// method describes a Java function. -type method struct { - id jdwp.MethodID - mod jdwp.ModBits - name string - sig methodSignature - class *Class -} - -func (m method) String() string { - args := make([]string, len(m.sig.Parameters)) - for i, a := range m.sig.Parameters { - args[i] = fmt.Sprintf("%v", a) - } - if m.name == constructor { - return fmt.Sprintf("%v %v(%v)", m.mod, m.class.String(), strings.Join(args, ", ")) - } - return fmt.Sprintf("%v %v %v.%v(%v)", m.mod, m.sig.Return, m.class.String(), m.name, strings.Join(args, ", ")) -} - -// methods is a list of methods. -type methods []method - -func (m methods) String() string { - lines := make([]string, len(m)) - for i, m := range m { - lines[i] = m.String() - } - return strings.Join(lines, "\n") -} - -// filter returns a copy of the method list with the methods that fail the -// predicate test removed. -func (m methods) filter(predicate func(m method) bool) methods { - out := make(methods, 0, len(m)) - for _, m := range m { - if predicate(m) { - out = append(out, m) - } - } - return out -} diff --git a/core/java/jdbg/resolve_overload.go b/core/java/jdbg/resolve_overload.go deleted file mode 100644 index cb33189c83..0000000000 --- a/core/java/jdbg/resolve_overload.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "bytes" - "fmt" - "strings" -) - -// resolveMethodErr is the error used when resolveMethod() fails. -type resolveMethodErr struct { - class *Class - name string - args []interface{} - candidates methods - problem string -} - -func (e resolveMethodErr) Error() string { - name := e.name - if name == constructor { - name = e.class.name - } - argTys := make([]string, len(e.args)) - for i, a := range e.args { - switch a := a.(type) { - case Value: - argTys[i] = fmt.Sprintf("%v", a.Type()) - default: - argTys[i] = fmt.Sprintf("%T", a) - } - } - sig := fmt.Sprintf("%v(%v)", name, strings.Join(argTys, ", ")) - problem := fmt.Sprintf(e.problem, sig) - if len(e.candidates) == 0 { - return problem - } - msg := bytes.Buffer{} - msg.WriteString(problem) - msg.WriteString("\n") - for _, candidate := range e.candidates { - line, marks := bytes.Buffer{}, bytes.Buffer{} - line.WriteString(candidate.name) - line.WriteRune('(') - marks.WriteString(strings.Repeat(" ", line.Len())) - for i, param := range candidate.sig.Parameters { - if i > 0 { - line.WriteString(", ") - marks.WriteString(" ") - } - ty := param.String() - line.WriteString(ty) - if i < len(e.args) && e.class.j.assignable(param, e.args[i]) { - marks.WriteString(strings.Repeat(" ", len(ty))) - } else { - marks.WriteString(strings.Repeat("^", len(ty))) - } - } - msg.WriteString(line.String()) - msg.WriteString(")\n") - msg.WriteString(marks.String()) - msg.WriteString("\n") - } - return msg.String() -} - -// resolveMethod attempts to unambiguously resolve a single method with the -// specified name and arguments from the class's method list. -// If considerSuper is true then the all base types will also be searched. -func (j *JDbg) resolveMethod(considerSuper bool, class *Class, name string, args []interface{}) method { - var methods []method - for search := class; search != nil; search = search.super { - methods = j.resolveMethods(search, name, args) - switch len(methods) { - case 0: - break - case 1: - return methods[0] - default: - j.err(resolveMethodErr{class, name, args, methods, "Ambiguous call to %v. Candidates:"}) - } - if !considerSuper { - break - } - } - resolved := class.resolve() - sameName := resolved.allMethods.filter(func(m method) bool { return m.name == name }) - if len(sameName) > 0 { - j.err(resolveMethodErr{class, name, args, sameName, "No methods match %v. Candidates:"}) - } - if considerSuper { - j.err(resolveMethodErr{class, name, args, resolved.allMethods, "No methods match %v. All methods:"}) - } - j.err(resolveMethodErr{class, name, args, resolved.methods, "No methods match %v. All methods:"}) - return method{} -} - -func (j *JDbg) resolveMethods(t *Class, name string, args []interface{}) []method { - candidates := t.resolve().methods.filter(func(m method) bool { - return m.name == name && len(m.sig.Parameters) == len(args) - }) - if len(candidates) == 0 { - return nil - } - return candidates.filter(func(m method) bool { return j.accepts(m.sig, args) }) -} diff --git a/core/java/jdbg/signature.go b/core/java/jdbg/signature.go deleted file mode 100644 index e69a9f4a3f..0000000000 --- a/core/java/jdbg/signature.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import "fmt" - -// parseSignature returns the type for the signature string starting at offset. -// offset will be modified so that it is one byte beyond the end of the parsed -// string. -func (j *JDbg) parseSignature(sig string, offset *int) (Type, error) { - r := sig[*offset] - *offset++ - switch r { - case 'V': - return j.cache.voidTy, nil - case 'Z': - return j.cache.boolTy, nil - case 'B': - return j.cache.byteTy, nil - case 'C': - return j.cache.charTy, nil - case 'S': - return j.cache.shortTy, nil - case 'I': - return j.cache.intTy, nil - case 'J': - return j.cache.longTy, nil - case 'F': - return j.cache.floatTy, nil - case 'D': - return j.cache.doubleTy, nil - case 'L': - // fully-qualified-class - start := *offset - 1 // include 'L' - for *offset < len(sig) { - r := sig[*offset] - *offset++ - if r == ';' { - return j.classFromSig(sig[start:*offset]) - } - } - return nil, fmt.Errorf("Fully qualified class missing terminating ';'") - case '[': - start := *offset - 1 // include '[' - el, err := j.parseSignature(sig, offset) - if err != nil { - return nil, err - } - sig := sig[start:*offset] - if array, ok := j.cache.arrays[sig]; ok { - return array, nil - } - class, err := j.classFromSig(sig) - if err != nil { - return nil, err - } - array := &Array{class, el} - j.cache.arrays[sig] = array - return array, nil - default: - return nil, fmt.Errorf("Unknown signature type tag '%v'", r) - } -} diff --git a/core/java/jdbg/type.go b/core/java/jdbg/type.go deleted file mode 100644 index 4ce3da0020..0000000000 --- a/core/java/jdbg/type.go +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "fmt" - - "github.com/google/gapid/core/java/jdwp" -) - -// Type represents a Java type. -type Type interface { - // String returns the string representation of the type. - String() string - // Signature returns the Java signature for the type. - Signature() string - // CastableTo returns true if this type can be cast to ty. - CastableTo(ty Type) bool - // Call invokes the static method with the specified arguments. - Call(method string, args ...interface{}) Value - - call(object Value, method string, args []interface{}) Value - field(object Value, name string) Value - jdbg() *JDbg -} - -// Simple is a primitive type. -type Simple struct { - j *JDbg - ty jdwp.Tag -} - -func (t *Simple) String() string { - switch t.ty { - case jdwp.TagVoid: - return "void" - case jdwp.TagBoolean: - return "boolean" - case jdwp.TagByte: - return "byte" - case jdwp.TagChar: - return "char" - case jdwp.TagShort: - return "short" - case jdwp.TagInt: - return "int" - case jdwp.TagLong: - return "long" - case jdwp.TagFloat: - return "float" - case jdwp.TagDouble: - return "double" - default: - return t.ty.String() - } -} - -// Signature returns the Java signature for the type. -func (t *Simple) Signature() string { - switch t.ty { - case jdwp.TagVoid: - return "V" - case jdwp.TagBoolean: - return "Z" - case jdwp.TagByte: - return "B" - case jdwp.TagChar: - return "C" - case jdwp.TagShort: - return "S" - case jdwp.TagInt: - return "I" - case jdwp.TagLong: - return "J" - case jdwp.TagFloat: - return "F" - case jdwp.TagDouble: - return "D" - default: - return t.ty.String() - } -} - -// CastableTo returns true if this type can be cast to ty. -func (t *Simple) CastableTo(ty Type) bool { - return t == ty || ty == t.j.ObjectType() -} - -// Call invokes the static method with the specified arguments. -func (t *Simple) Call(method string, args ...interface{}) Value { - return t.call(Value{}, method, args) -} - -func (t *Simple) call(object Value, method string, args []interface{}) Value { - t.j.fail("Type '%v' does not support methods", t.ty) - return Value{} -} - -func (t *Simple) field(object Value, name string) Value { - t.j.fail("Type '%v' does not support fields", t.ty) - return Value{} -} - -func (t *Simple) jdbg() *JDbg { return t.j } - -// Array is the type of an array. -type Array struct { - *Class - el Type -} - -func (t *Array) String() string { return fmt.Sprintf("%v[]", t.el) } - -// CastableTo returns true if this type can be cast to ty. -func (t *Array) CastableTo(ty Type) bool { - if t == ty { - return true - } - return t.Class.CastableTo(ty) -} - -// New constructs a new array of the specified size. -func (t *Array) New(size int) Value { - array, err := t.j.conn.NewArray(jdwp.ArrayTypeID(t.class.TypeID), size) - if err != nil { - t.j.fail("Failed to create array: %v", err) - } - if array.Type != jdwp.TagArray { - t.j.fail("NewArray returned %v, not array", array.Type) - } - return newValue(t, jdwp.ArrayID(array.Object)) -} - -type classResolvedInfo struct { - methods methods - fields jdwp.Fields - interfaces []*Class - allMethods methods // Include super's allMethods - error error -} - -// Class is the type of an object. -type Class struct { - j *JDbg - signature string - name string - class jdwp.ClassInfo - implements []*Class - fields jdwp.Fields - super *Class - resolved *classResolvedInfo -} - -func (t *Class) String() string { return t.name } - -// ID returns the JDWP class identifier. -func (t *Class) ID() jdwp.ClassID { return t.class.ClassID() } - -// Signature returns the Java signature for the type. -func (t *Class) Signature() string { return t.signature } - -// New returns a new instance of the class type using the specified parameters. -func (t *Class) New(args ...interface{}) Value { - m := t.j.resolveMethod(false, t, constructor, args) - values := t.j.marshalN(args) - res, err := t.j.conn.NewInstance( - m.class.class.ClassID(), m.id, t.j.thread, jdwp.InvokeSingleThreaded, values...) - if err != nil { - t.j.fail("NewInstance() returned: %v", err) - } - t.j.errFromException(res.Exception, m) - return newValue(t, res.Result) -} - -// CastableTo returns true if this type can be cast to ty. -func (t *Class) CastableTo(ty Type) bool { - if t == ty { - return true - } - for _, i := range t.implements { - if i.CastableTo(ty) { - return true - } - } - if t.super != nil { - return t.super.CastableTo(ty) - } - return false -} - -// Call invokes a static method on the class. -func (t *Class) Call(method string, args ...interface{}) Value { - return t.call(Value{}, method, args) -} - -// Field returns the value of the static field with the given name. -func (t *Class) Field(name string) Value { - field := t.resolve().fields.FindByName(name) - values, err := t.j.conn.GetStaticFieldValues(t.class.TypeID, field.ID) - if err != nil { - t.j.fail("GetValues() returned: %v", err) - } - return t.j.value(values[0]) -} - -// Super returns the super class type. -func (t *Class) Super() *Class { - return t.super -} - -func (t *Class) call(object Value, method string, args []interface{}) Value { - m := t.j.resolveMethod(object != nilValue, t, method, args) - values := t.j.marshalN(args) - - var res jdwp.InvokeResult - var err error - if m.mod&jdwp.ModStatic != 0 { - res, err = t.j.conn.InvokeStaticMethod( - t.class.ClassID(), m.id, t.j.thread, jdwp.InvokeSingleThreaded, values...) - } else { - if object == nilValue { - t.j.fail("Cannot call non-static method '%v' without an object", method) - } - var obj interface{} - obj = object.val - object, ok := obj.(jdwp.Object) - if !ok { - t.j.fail("Cannot call methods on %T types", obj) - } - res, err = t.j.conn.InvokeMethod( - object.ID(), t.class.ClassID(), m.id, t.j.thread, jdwp.InvokeSingleThreaded, values...) - } - if err != nil { - t.j.err(err) - } - - t.j.errFromException(res.Exception, m) - - result, isResultObject := res.Result.(jdwp.Object) - if !isResultObject { - if _, expectedClass := m.sig.Return.(*Class); expectedClass { - panic(fmt.Errorf("Call of %v returned value %T(%v) when %v was expected", - method, res.Result, res.Result, m.sig.Return)) - } - return newValue(m.sig.Return, res.Result) - } - - if result.ID() == 0 { - return Value{m.sig.Return, result.ID()} // null pointer - } - - tyID, err := t.j.conn.GetObjectType(result.ID()) - if err != nil { - t.j.fail("GetObjectType() returned: %v", err) - } - - ty := t.j.typeFromID(tyID.Type) - if !ty.CastableTo(m.sig.Return) { - t.j.fail("Call of %v returned type %v which is not castable to %v", - method, ty, m.sig.Return) - } - - return newValue(ty, res.Result) -} - -func (t *Class) field(object Value, name string) Value { - if object == nilValue { - t.j.fail("Cannot get field '%v' on nill object", name) - } - f := t.fields.FindByName(name) - if f == nil { - t.j.fail("Class '%v' does not contain field '%v'", t.name, name) - } - obj, ok := object.val.(jdwp.Object) - if !ok { - t.j.fail("Class '%v' does not support fields", t.name) - } - vals, err := t.j.conn.GetFieldValues(obj.ID(), f.ID) - if err != nil { - t.j.fail("GetFieldValues() returned: %v", err) - } - if len(vals) != 1 { - t.j.fail("GetFieldValues() returned %n values, expected 1", len(vals)) - } - return t.j.value(vals[0]) -} - -func (t *Class) jdbg() *JDbg { return t.j } - -func (t *Class) resolve() *classResolvedInfo { - if t.resolved != nil { - return t.resolved - } - t.resolved = &classResolvedInfo{} - - f, err := t.j.conn.GetFields(t.class.TypeID) - if err != nil { - t.resolved.error = err - return t.resolved - } - t.resolved.fields = f - - m, err := t.j.conn.GetMethods(t.class.TypeID) - if err != nil { - t.resolved.error = err - return t.resolved - } - t.resolved.methods = make([]method, 0, len(m)) - for _, m := range m { - sig, err := t.j.parseMethodSignature(m.Signature) - if err != nil { - // Probably uses a type that hasn't been loaded. Ignore. - continue - } - t.resolved.methods = append(t.resolved.methods, method{ - id: m.ID, - mod: m.ModBits, - name: m.Name, - sig: sig, - class: t, - }) - } - // build allMethods from methods and super's allMethods - var superMethods methods - if t.super != nil { - if resolved := t.super.resolve(); resolved.error == nil { - superMethods = resolved.allMethods - } - } - all := make(methods, 0, len(t.resolved.methods)+len(superMethods)) - t.resolved.allMethods = append(append(all, t.resolved.methods...), superMethods...) - - return t.resolved -} - -func (j *JDbg) errFromException(exception jdwp.TaggedObjectID, method method) { - if (exception.Type == 0 || exception.Type == jdwp.TagObject) && exception.Object == 0 { - return - } - str := j.object(exception.Object).Call("toString").Get() - j.fail("Exception raised calling: %v\n%v", method, str) -} diff --git a/core/java/jdbg/value.go b/core/java/jdbg/value.go deleted file mode 100644 index c6b66759ec..0000000000 --- a/core/java/jdbg/value.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdbg - -import ( - "fmt" - "reflect" - - "github.com/google/gapid/core/java/jdwp" -) - -var nilValue Value - -// Value holds the value of a call. -type Value struct { - ty Type - val interface{} -} - -func newValue(ty Type, val interface{}) Value { - if obj, ok := val.(jdwp.Object); ok { - // Prevent GC of this object for the duration of the jdbg.Do call. - j, id := ty.jdbg(), obj.ID() - j.conn.DisableGC(id) - j.objects = append(j.objects, id) - } - return Value{ty, val} -} - -// Call invokes the method on the value. -func (v Value) Call(method string, args ...interface{}) Value { - return v.ty.call(v, method, args) -} - -// Field returns the value of the specified field. -func (v Value) Field(name string) Value { - return v.ty.field(v, name) -} - -// Get returns the value, unmarshalled. -func (v Value) Get() interface{} { - return v.ty.jdbg().unmarshal(v.val) -} - -// Type returns the value's type. -func (v Value) Type() Type { - return v.ty -} - -// AsType returns the value as a type. -func (v Value) AsType() Type { - j := v.ty.jdbg() - switch v := v.val.(type) { - case jdwp.ClassObjectID: - id, err := j.conn.ReflectedType(v) - if err != nil { - j.fail("%v", err) - } - return j.typeFromID(id) - } - panic(fmt.Errorf("Unhandled value type: %T %+v", v.val, v.val)) -} - -// SetArrayValues sets the array values to values. This value must be an Array. -func (v Value) SetArrayValues(values interface{}) { - j := v.ty.jdbg() - arrayTy, ok := v.ty.(*Array) - if !ok { - j.fail("SetArrayValues can only be used with Arrays, type is %v", v.ty) - } - r := reflect.ValueOf(values) - if r.Kind() != reflect.Array && r.Kind() != reflect.Slice { - j.fail("values must be an array or slice, got %v", r.Kind()) - } - - if !j.assignableT(v.ty, reflect.TypeOf(values)) { - j.fail("value elements (type %T) does not match array element type %v", values, arrayTy.el) - } - - if arrayTy.el == j.cache.objTy { - values = j.toObjects(values.([]interface{})) - } - - if err := j.conn.SetArrayValues(v.val.(jdwp.ArrayID), 0, values); err != nil { - j.fail("Failed to set array (type %s) values (type %T): %v", arrayTy, values, err) - } -} - -// Variable is a named Value. -type Variable struct { - Value Value - variable jdwp.VariableRequest -} diff --git a/core/java/jdwp/BUILD.bazel b/core/java/jdwp/BUILD.bazel deleted file mode 100644 index 12f80d821d..0000000000 --- a/core/java/jdwp/BUILD.bazel +++ /dev/null @@ -1,77 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = [ - "class_status.go", - "cmdset_arrayreference.go", - "cmdset_arraytype.go", - "cmdset_classobjectreference.go", - "cmdset_classtype.go", - "cmdset_eventrequest.go", - "cmdset_method.go", - "cmdset_objectreference.go", - "cmdset_referencetypes.go", - "cmdset_stackframe.go", - "cmdset_stringreference.go", - "cmdset_threadreference.go", - "cmdset_vm.go", - "cmdsets.go", - "coder.go", - "debug.go", - "errors.go", - "event.go", - "event_kind.go", - "field.go", - "helpers.go", - "invoke_options.go", - "jdwp.go", - "method.go", - "modbits.go", - "packet.go", - "recv.go", - "suspend_policy.go", - "tag.go", - "thread_status.go", - "type_tag.go", - "types.go", - "value.go", - ], - importpath = "github.com/google/gapid/core/java/jdwp", - visibility = ["//visibility:public"], - deps = [ - "//core/app/crash:go_default_library", - "//core/data/binary:go_default_library", - "//core/data/endian:go_default_library", - "//core/event/task:go_default_library", - "//core/log:go_default_library", - "//core/os/device:go_default_library", - ], -) - -go_test( - name = "go_default_test", - size = "small", - srcs = ["jdwp_test.go"], - embed = [":go_default_library"], - tags = ["integration"], - deps = [ - "//core/assert:go_default_library", - "//core/java/jdwp/test:go_default_library", - "//core/log:go_default_library", - ], -) diff --git a/core/java/jdwp/class_status.go b/core/java/jdwp/class_status.go deleted file mode 100644 index a9c82d5dbc..0000000000 --- a/core/java/jdwp/class_status.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "strings" - -// ClassStatus is an enumerator of class loading state. -// See https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html#jvms-5.3 -// for detailed descriptions of the loading states. -type ClassStatus int - -const ( - // StatusVerified is used to describe a class in the verified state. - StatusVerified = ClassStatus(1) - // StatusPrepared is used to describe a class in the prepared state. - StatusPrepared = ClassStatus(2) - // StatusInitialized is used to describe a class in the initialized state. - StatusInitialized = ClassStatus(4) - // StatusError is used to describe a class in the error state. - StatusError = ClassStatus(8) -) - -func (c ClassStatus) String() string { - parts := []string{} - if c&StatusVerified != 0 { - parts = append(parts, "Verified") - } - if c&StatusPrepared != 0 { - parts = append(parts, "Prepared") - } - if c&StatusInitialized != 0 { - parts = append(parts, "Initialized") - } - if c&StatusError != 0 { - parts = append(parts, "Error") - } - return strings.Join(parts, ", ") -} diff --git a/core/java/jdwp/cmdset_arrayreference.go b/core/java/jdwp/cmdset_arrayreference.go deleted file mode 100644 index 3f99f4e30f..0000000000 --- a/core/java/jdwp/cmdset_arrayreference.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// GetArrayLength returns the length of the specified array. -func (c *Connection) GetArrayLength(id ArrayID) (int, error) { - var res int - err := c.get(cmdArrayReferenceLength, id, &res) - return res, err -} - -// GetArrayValues the values of the specified array. -func (c *Connection) GetArrayValues(id ArrayID, first, length int) ([]Value, error) { - req := struct { - ID ArrayID - First int - Length int - }{id, first, length} - var res []Value - err := c.get(cmdArrayReferenceGetValues, req, &res) - return res, err -} - -// SetArrayValues the values of the specified array. -func (c *Connection) SetArrayValues(id ArrayID, first int, values interface{}) error { - req := struct { - ID ArrayID - First int - Values interface{} - }{id, first, values} - return c.get(cmdArrayReferenceSetValues, req, nil) -} diff --git a/core/java/jdwp/cmdset_arraytype.go b/core/java/jdwp/cmdset_arraytype.go deleted file mode 100644 index 71eec05535..0000000000 --- a/core/java/jdwp/cmdset_arraytype.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// NewArray constructs a new array of the specified type and length. -func (c *Connection) NewArray(ty ArrayTypeID, length int) (TaggedObjectID, error) { - req := struct { - Ty ArrayTypeID - Length int - }{ty, length} - var res TaggedObjectID - err := c.get(cmdArrayTypeNewInstance, req, &res) - return res, err -} diff --git a/core/java/jdwp/cmdset_classobjectreference.go b/core/java/jdwp/cmdset_classobjectreference.go deleted file mode 100644 index 020fd80be8..0000000000 --- a/core/java/jdwp/cmdset_classobjectreference.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// ReflectedType returns the reference type reflected by the class object. -func (c *Connection) ReflectedType(id ClassObjectID) (ReferenceTypeID, error) { - req := struct { - ID ClassObjectID - }{id} - var res struct { - Kind byte - ID ReferenceTypeID - } - err := c.get(cmdClassObjectReferenceReflectedType, req, &res) - return res.ID, err -} diff --git a/core/java/jdwp/cmdset_classtype.go b/core/java/jdwp/cmdset_classtype.go deleted file mode 100644 index 378610e839..0000000000 --- a/core/java/jdwp/cmdset_classtype.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// InvokeResult holds the return values for a method invokation. -type InvokeResult struct { - Result Value - Exception TaggedObjectID -} - -// GetSuperClass returns the immediate super class of the specified class. -func (c *Connection) GetSuperClass(class ClassID) (ClassID, error) { - var res ClassID - err := c.get(cmdClassTypeSuperclass, class, &res) - return res, err -} - -// InvokeStaticMethod invokes the specified static method. -func (c *Connection) InvokeStaticMethod(class ClassID, method MethodID, thread ThreadID, options InvokeOptions, args ...Value) (InvokeResult, error) { - req := struct { - Class ClassID - Thread ThreadID - Method MethodID - Args []Value - Options InvokeOptions - }{class, thread, method, args, options} - var res InvokeResult - err := c.get(cmdClassTypeInvokeMethod, req, &res) - return res, err -} - -// NewInstanceResult holds the return values for a constructor invokation. -type NewInstanceResult struct { - Result TaggedObjectID - Exception TaggedObjectID -} - -// NewInstance invokes the specified constructor. -func (c *Connection) NewInstance(class ClassID, constructor MethodID, thread ThreadID, options InvokeOptions, args ...Value) (NewInstanceResult, error) { - req := struct { - Class ClassID - Thread ThreadID - Constructor MethodID - Args []Value - Options InvokeOptions - }{class, thread, constructor, args, options} - var res NewInstanceResult - err := c.get(cmdClassTypeNewInstance, req, &res) - return res, err -} diff --git a/core/java/jdwp/cmdset_eventrequest.go b/core/java/jdwp/cmdset_eventrequest.go deleted file mode 100644 index 88b1a55663..0000000000 --- a/core/java/jdwp/cmdset_eventrequest.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "context" - "fmt" - - "github.com/google/gapid/core/event/task" -) - -// EventRequestID is an identifier of an event request. -type EventRequestID int - -const cmdCompositeEvent = cmdID(100) - -// WatchEvents sets an event watcher, calling handler for each received event. -// WatchEvents will continue to watch for events until handler returns false or -// the context is cancelled. -func (c *Connection) WatchEvents( - ctx context.Context, - kind EventKind, - suspendPolity SuspendPolicy, - handler func(Event) bool, - modifiers ...EventModifier) error { - - req := struct { - Kind EventKind - SuspendPolicy SuspendPolicy - Modifiers []EventModifier - }{ - Kind: kind, - SuspendPolicy: suspendPolity, - Modifiers: modifiers, - } - - var id EventRequestID - err := c.get(cmdEventRequestSet, req, &id) - if err != nil { - return err - } - - events := make(chan Event, 8) - c.Lock() - c.events[id] = events - c.Unlock() - - defer func() { - c.Lock() - delete(c.events, id) - c.Unlock() - }() - - if err := c.ResumeAll(); err != nil { - return err - } - -run: // Consume events until the handler returns false or the context is cancelled. - for { - select { - case event := <-events: - if !handler(event) { - break run - } - case <-task.ShouldStop(ctx): - break run - } - } - - // Clear the event. - clear := struct { - Kind EventKind - ID EventRequestID - }{ - Kind: kind, - ID: id, - } - - if err := c.get(cmdEventRequestClear, clear, nil); err != nil { - return err - } - -flush: // Consume any remaining events in the pipe. - for { - select { - case event := <-events: - handler(event) - default: - break flush - } - } - - return nil -} - -// EventModifier is the interface implemented by all event modifier types. -// These are filters on the events that are raised. -// See http://docs.oracle.com/javase/1.5.0/docs/guide/jpda/jdwp/jdwp-protocol.html#JDWP_EventRequest_Set -// for detailed descriptions and rules for each of the EventModifiers. -type EventModifier interface { - modKind() uint8 -} - -// CountEventModifier is an EventModifier that limits the number of times an -// event is fired. For example, using a CountEventModifier of 2 will only let -// two events fire. -type CountEventModifier int - -// ThreadOnlyEventModifier is an EventModifier that filters the events to those -// that are raised on the specified thread. -type ThreadOnlyEventModifier ThreadID - -// ClassOnlyEventModifier is an EventModifier that filters the events to those -// that are associated with the specified class. -type ClassOnlyEventModifier ClassID - -// ClassMatchEventModifier is an EventModifier that filters the events to those -// that are associated with class names that match the pattern. The pattern can -// be an exact class name match, for use a '*' wildcard at the start or end of -// the string. Examples: -// • "java.lang.String" -// • "*.String" -// • "java.lang.*" -type ClassMatchEventModifier string - -// ClassExcludeEventModifier is an EventModifier that filters the events to -// those that are not associated with class names that match the pattern. -// See ClassMatchEventModifier for the permitted patterns. -type ClassExcludeEventModifier string - -// LocationOnlyEventModifier is an EventModifier that filters the events to -// those that only originate at the specified location. -type LocationOnlyEventModifier Location - -// ExceptionOnlyEventModifier is an EventModifier that filters exception events. -// Can only be used for exception events. -type ExceptionOnlyEventModifier struct { - ExceptionOrNull ReferenceTypeID // If not nil, only permit exceptions of this type. - Caught bool // Report caught exceptions - Uncaught bool // Report uncaught exceptions -} - -// FieldOnlyEventModifier is an EventModifier that filters events to those -// relating to the specified field. -// Can only be used for field access or field modified events. -type FieldOnlyEventModifier struct { - Type ReferenceTypeID - Field FieldID -} - -// StepEventModifier is an EventModifier that filters step events to those which -// satisfy depth and size constraints. -// Can only be used with step events. -type StepEventModifier struct { - Thread ThreadID - Size int - Depth int -} - -// InstanceOnlyEventModifier is an EventModifier that filters events to those -// which have the specified 'this' object. -type InstanceOnlyEventModifier ObjectID - -func (CountEventModifier) modKind() uint8 { return 1 } -func (ThreadOnlyEventModifier) modKind() uint8 { return 3 } -func (ClassOnlyEventModifier) modKind() uint8 { return 4 } -func (ClassMatchEventModifier) modKind() uint8 { return 5 } -func (ClassExcludeEventModifier) modKind() uint8 { return 6 } -func (LocationOnlyEventModifier) modKind() uint8 { return 7 } -func (ExceptionOnlyEventModifier) modKind() uint8 { return 8 } -func (FieldOnlyEventModifier) modKind() uint8 { return 9 } -func (StepEventModifier) modKind() uint8 { return 10 } -func (InstanceOnlyEventModifier) modKind() uint8 { return 11 } - -func (m CountEventModifier) String() string { - return fmt.Sprintf("CountEventModifier<%v>", int(m)) -} -func (m ThreadOnlyEventModifier) String() string { - return fmt.Sprintf("ThreadOnlyEventModifier<%v>", int(m)) -} -func (m ClassOnlyEventModifier) String() string { - return fmt.Sprintf("ClassOnlyEventModifier<%v>", int(m)) -} -func (m ClassMatchEventModifier) String() string { - return fmt.Sprintf("ClassMatchEventModifier<%v>", string(m)) -} -func (m ClassExcludeEventModifier) String() string { - return fmt.Sprintf("ClassExcludeEventModifier<%v>", string(m)) -} -func (m LocationOnlyEventModifier) String() string { - return fmt.Sprintf("LocationOnlyEventModifier<%v>", Location(m)) -} -func (m ExceptionOnlyEventModifier) String() string { - return fmt.Sprintf("ExceptionOnlyEventModifier", - m.ExceptionOrNull, m.Caught, m.Uncaught) -} -func (m FieldOnlyEventModifier) String() string { - return fmt.Sprintf("FieldOnlyEventModifier", m.Type, m.Field) -} -func (m StepEventModifier) String() string { - return fmt.Sprintf("StepEventModifier", - m.Thread, m.Size, m.Depth) -} -func (m InstanceOnlyEventModifier) String() string { - return fmt.Sprintf("InstanceOnlyEventModifier<%v>", ObjectID(m)) -} diff --git a/core/java/jdwp/cmdset_method.go b/core/java/jdwp/cmdset_method.go deleted file mode 100644 index 560cf2bf55..0000000000 --- a/core/java/jdwp/cmdset_method.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// VariableTable returns all of the variables that are present in the given -// Method. -func (c *Connection) VariableTable(classTy ReferenceTypeID, method MethodID) (VariableTable, error) { - req := struct { - Class ReferenceTypeID - Method MethodID - }{classTy, method} - var res VariableTable - err := c.get(cmdMethodTypeVariableTable, req, &res) - return res, err -} diff --git a/core/java/jdwp/cmdset_objectreference.go b/core/java/jdwp/cmdset_objectreference.go deleted file mode 100644 index 4478a4c63a..0000000000 --- a/core/java/jdwp/cmdset_objectreference.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// ObjectType describes a Java type. -type ObjectType struct { - Kind TypeTag - Type ReferenceTypeID -} - -// GetObjectType returns the type of the specified object. -func (c *Connection) GetObjectType(object ObjectID) (ObjectType, error) { - var res ObjectType - err := c.get(cmdObjectReferenceReferenceType, object, &res) - return res, err -} - -// GetFieldValues returns the values of all the instance fields. -func (c *Connection) GetFieldValues(obj ObjectID, fields ...FieldID) ([]Value, error) { - var res []Value - err := c.get(cmdObjectReferenceGetValues, struct { - Obj ObjectID - Fields []FieldID - }{obj, fields}, &res) - return res, err -} - -// InvokeMethod invokes the specified static method. -func (c *Connection) InvokeMethod(object ObjectID, class ClassID, method MethodID, thread ThreadID, options InvokeOptions, args ...Value) (InvokeResult, error) { - req := struct { - Object ObjectID - Thread ThreadID - Class ClassID - Method MethodID - Args []Value - Options InvokeOptions - }{object, thread, class, method, args, options} - var res InvokeResult - err := c.get(cmdObjectReferenceInvokeMethod, req, &res) - return res, err -} - -// DisableGC disables garbage collection for the specified object. -func (c *Connection) DisableGC(object ObjectID) error { - return c.get(cmdObjectReferenceDisableCollection, object, nil) -} - -// EnableGC enables garbage collection for the specified object. -func (c *Connection) EnableGC(object ObjectID) error { - return c.get(cmdObjectReferenceEnableCollection, object, nil) -} diff --git a/core/java/jdwp/cmdset_referencetypes.go b/core/java/jdwp/cmdset_referencetypes.go deleted file mode 100644 index 26909bca87..0000000000 --- a/core/java/jdwp/cmdset_referencetypes.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// GetTypeSignature returns the Java type signature for the specified type. -func (c *Connection) GetTypeSignature(ty ReferenceTypeID) (string, error) { - var res string - err := c.get(cmdReferenceTypeSignature, ty, &res) - return res, err -} - -// GetFields returns all the fields for the specified type. -func (c *Connection) GetFields(ty ReferenceTypeID) (Fields, error) { - var res Fields - err := c.get(cmdReferenceTypeFields, ty, &res) - return res, err -} - -// GetMethods returns all the methods for the specified type. -func (c *Connection) GetMethods(ty ReferenceTypeID) (Methods, error) { - var res Methods - err := c.get(cmdReferenceTypeMethods, ty, &res) - return res, err -} - -// GetStaticFieldValues returns the values of all the requests static fields. -func (c *Connection) GetStaticFieldValues(ty ReferenceTypeID, fields ...FieldID) ([]Value, error) { - var res []Value - err := c.get(cmdReferenceTypeGetValues, struct { - Ty ReferenceTypeID - Fields []FieldID - }{ty, fields}, &res) - return res, err -} - -// GetImplemented returns all the direct interfaces implemented by the specified -// type. -func (c *Connection) GetImplemented(ty ReferenceTypeID) ([]InterfaceID, error) { - var res []InterfaceID - err := c.get(cmdReferenceTypeInterfaces, ty, &res) - return res, err -} diff --git a/core/java/jdwp/cmdset_stackframe.go b/core/java/jdwp/cmdset_stackframe.go deleted file mode 100644 index 9ea91cdcac..0000000000 --- a/core/java/jdwp/cmdset_stackframe.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// GetThisObject returns the this object for the specified thread and stack -// frame. -func (c *Connection) GetThisObject(thread ThreadID, frame FrameID) (TaggedObjectID, error) { - req := struct { - Thread ThreadID - Frame FrameID - }{thread, frame} - res := TaggedObjectID{} - err := c.get(cmdStackFrameThisObject, req, &res) - return res, err -} - -type VariableRequest struct { - Index int - Tag uint8 -} - -// GetValues returns the set of objects for the specified thread and frame, -// based on their slots. -func (c *Connection) GetValues(thread ThreadID, frame FrameID, slots []VariableRequest) ([]Value, error) { - req := struct { - Thread ThreadID - Frame FrameID - Slots []VariableRequest - }{thread, frame, slots} - res := ValueSlice{} - err := c.get(cmdStackFrameGetValues, req, &res) - return res, err -} - -type VariableAssignmentRequest struct { - Index int - Value Value -} - -// SetValues sets the values for the local variables given thread and frame -func (c *Connection) SetValues(thread ThreadID, frame FrameID, slots []VariableAssignmentRequest) error { - req := struct { - Thread ThreadID - Frame FrameID - Slots []VariableAssignmentRequest - }{thread, frame, slots} - - err := c.get(cmdStackFrameSetValues, req, nil) - return err -} diff --git a/core/java/jdwp/cmdset_stringreference.go b/core/java/jdwp/cmdset_stringreference.go deleted file mode 100644 index 74e1cfa863..0000000000 --- a/core/java/jdwp/cmdset_stringreference.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// GetString returns the string text for the given StringID. -func (c *Connection) GetString(id StringID) (string, error) { - var res string - err := c.get(cmdStringReferenceValue, id, &res) - return res, err -} diff --git a/core/java/jdwp/cmdset_threadreference.go b/core/java/jdwp/cmdset_threadreference.go deleted file mode 100644 index dd15a112ba..0000000000 --- a/core/java/jdwp/cmdset_threadreference.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// GetThreadName returns a thread's name. -func (c *Connection) GetThreadName(id ThreadID) (string, error) { - var res string - err := c.get(cmdThreadReferenceName, id, &res) - return res, err -} - -// Suspend suspends the specified thread. -func (c *Connection) Suspend(id ThreadID) error { - var res struct{} - return c.get(cmdThreadReferenceSuspend, id, &res) -} - -// Resume resumes the specified thread. -func (c *Connection) Resume(id ThreadID) error { - var res struct{} - return c.get(cmdThreadReferenceResume, id, &res) -} - -// GetThreadStatus returns the status of the thread. -func (c *Connection) GetThreadStatus(id ThreadID) (ThreadStatus, SuspendStatus, error) { - var res struct { - T ThreadStatus - S SuspendStatus - } - err := c.get(cmdThreadReferenceStatus, id, &res) - if err != nil { - return 0, 0, err - } - return res.T, res.S, nil -} - -// GetSuspendCount returns the number of times the thread has been suspended -// without a corresponding resume. -func (c *Connection) GetSuspendCount(id ThreadID) (int, error) { - var count int - err := c.get(cmdThreadReferenceSuspendCount, id, &count) - if err != nil { - return 0, err - } - return count, nil -} - -// FrameInfo describes a single stack frame. -type FrameInfo struct { - Frame FrameID - Location Location -} - -// GetFrames returns a number of stack frames. -func (c *Connection) GetFrames(thread ThreadID, start, count int) ([]FrameInfo, error) { - req := struct { - Thread ThreadID - Start, Count int - }{thread, start, count} - var res []FrameInfo - err := c.get(cmdThreadReferenceFrames, req, &res) - return res, err -} diff --git a/core/java/jdwp/cmdset_vm.go b/core/java/jdwp/cmdset_vm.go deleted file mode 100644 index 3fc41019ee..0000000000 --- a/core/java/jdwp/cmdset_vm.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// Version describes the JDWP version -type Version struct { - Description string // Text information on the VM version - JDWPMajor int // Major JDWP Version number - JDWPMinor int // Minor JDWP Version number - Version string // Target VM JRE version, as in the java.version property - Name string // Target VM name, as in the java.vm.name property -} - -// GetVersion returns the JDWP version from the server. -func (c *Connection) GetVersion() (Version, error) { - res := Version{} - err := c.get(cmdVirtualMachineVersion, struct{}{}, &res) - return res, err -} - -// ClassInfo describes a loaded classes matching the requested signature. -type ClassInfo struct { - Kind TypeTag // Kind of reference type - TypeID ReferenceTypeID // Matching loaded reference type - Signature string // The class signature - Status ClassStatus // The class status -} - -// ClassID returns the class identifier for the ClassBySignature. -func (c ClassInfo) ClassID() ClassID { - return ClassID(c.TypeID) -} - -// GetClassesBySignature returns all the loaded classes matching the requested -// signature from the server. -func (c *Connection) GetClassesBySignature(signature string) ([]ClassInfo, error) { - res := []struct { - Kind TypeTag - TypeID ReferenceTypeID - Status ClassStatus - }{} - err := c.get(cmdVirtualMachineClassesBySignature, &signature, &res) - out := make([]ClassInfo, len(res)) - for i, c := range res { - out[i] = ClassInfo{c.Kind, c.TypeID, signature, c.Status} - } - return out, err -} - -// GetAllClasses returns all the active threads by ID. -func (c *Connection) GetAllClasses() ([]ClassInfo, error) { - res := []ClassInfo{} - err := c.get(cmdVirtualMachineAllClasses, struct{}{}, &res) - return res, err -} - -// GetAllThreads returns all the active threads by ID. -func (c *Connection) GetAllThreads() ([]ThreadID, error) { - res := []ThreadID{} - err := c.get(cmdVirtualMachineAllThreads, struct{}{}, &res) - return res, err -} - -// IDSizes describes the sizes of all the variably sized data types. -type IDSizes struct { - FieldIDSize int32 // FieldID size in bytes - MethodIDSize int32 // MethodID size in bytes - ObjectIDSize int32 // ObjectID size in bytes - ReferenceTypeIDSize int32 // ReferenceTypeID size in bytes - FrameIDSize int32 // FrameID size in bytes -} - -// GetIDSizes returns the sizes of all the variably sized data types. -func (c *Connection) GetIDSizes() (IDSizes, error) { - res := IDSizes{} - err := c.get(cmdVirtualMachineIDSizes, struct{}{}, &res) - return res, err -} - -// SuspendAll suspends all threads. -func (c *Connection) SuspendAll() error { - return c.get(cmdVirtualMachineSuspend, struct{}{}, nil) -} - -// ResumeAll resumes all threads. -func (c *Connection) ResumeAll() error { - return c.get(cmdVirtualMachineResume, struct{}{}, nil) -} - -// ResumeAllExcept resumes all threads except for the specified thread. -func (c *Connection) ResumeAllExcept(thread ThreadID) error { - if err := c.Suspend(thread); err != nil { - return err - } - return c.ResumeAll() -} - -// CreateString returns the StringID for the given string. -func (c *Connection) CreateString(str string) (StringID, error) { - res := StringID(0) - err := c.get(cmdVirtualMachineCreateString, str, &res) - return res, err -} diff --git a/core/java/jdwp/cmdsets.go b/core/java/jdwp/cmdsets.go deleted file mode 100644 index 0aaac17aa7..0000000000 --- a/core/java/jdwp/cmdsets.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// cmdSet is the namespace for a command identifier. -type cmdSet uint8 - -// cmdID is a command in a command set. -type cmdID uint8 - -type cmd struct { - set cmdSet - id cmdID -} - -func (c cmd) String() string { - return fmt.Sprintf("%v.%v", c.set, cmdNames[c]) -} - -const ( - cmdSetVirtualMachine = cmdSet(1) - cmdSetReferenceType = cmdSet(2) - cmdSetClassType = cmdSet(3) - cmdSetArrayType = cmdSet(4) - cmdSetInterfaceType = cmdSet(5) - cmdSetMethod = cmdSet(6) - cmdSetField = cmdSet(8) - cmdSetObjectReference = cmdSet(9) - cmdSetStringReference = cmdSet(10) - cmdSetThreadReference = cmdSet(11) - cmdSetThreadGroupReference = cmdSet(12) - cmdSetArrayReference = cmdSet(13) - cmdSetClassLoaderReference = cmdSet(14) - cmdSetEventRequest = cmdSet(15) - cmdSetStackFrame = cmdSet(16) - cmdSetClassObjectReference = cmdSet(17) - cmdSetEvent = cmdSet(64) -) - -func (c cmdSet) String() string { - switch c { - case cmdSetVirtualMachine: - return "VirtualMachine" - case cmdSetReferenceType: - return "ReferenceType" - case cmdSetClassType: - return "ClassType" - case cmdSetArrayType: - return "ArrayType" - case cmdSetInterfaceType: - return "InterfaceType" - case cmdSetMethod: - return "Method" - case cmdSetField: - return "Field" - case cmdSetObjectReference: - return "ObjectReference" - case cmdSetStringReference: - return "StringReference" - case cmdSetThreadReference: - return "ThreadReference" - case cmdSetThreadGroupReference: - return "ThreadGroupReference" - case cmdSetArrayReference: - return "ArrayReference" - case cmdSetClassLoaderReference: - return "ClassLoaderReference" - case cmdSetEventRequest: - return "EventRequest" - case cmdSetStackFrame: - return "StackFrame" - case cmdSetClassObjectReference: - return "ClassObjectReference" - case cmdSetEvent: - return "Event" - } - return fmt.Sprint(int(c)) -} - -var ( - cmdVirtualMachineVersion = cmd{cmdSetVirtualMachine, 1} - cmdVirtualMachineClassesBySignature = cmd{cmdSetVirtualMachine, 2} - cmdVirtualMachineAllClasses = cmd{cmdSetVirtualMachine, 3} - cmdVirtualMachineAllThreads = cmd{cmdSetVirtualMachine, 4} - cmdVirtualMachineTopLevelThreadGroups = cmd{cmdSetVirtualMachine, 5} - cmdVirtualMachineDispose = cmd{cmdSetVirtualMachine, 6} - cmdVirtualMachineIDSizes = cmd{cmdSetVirtualMachine, 7} - cmdVirtualMachineSuspend = cmd{cmdSetVirtualMachine, 8} - cmdVirtualMachineResume = cmd{cmdSetVirtualMachine, 9} - cmdVirtualMachineExit = cmd{cmdSetVirtualMachine, 10} - cmdVirtualMachineCreateString = cmd{cmdSetVirtualMachine, 11} - cmdVirtualMachineCapabilities = cmd{cmdSetVirtualMachine, 12} - cmdVirtualMachineClassPaths = cmd{cmdSetVirtualMachine, 13} - cmdVirtualMachineDisposeObjects = cmd{cmdSetVirtualMachine, 14} - cmdVirtualMachineHoldEvents = cmd{cmdSetVirtualMachine, 15} - cmdVirtualMachineReleaseEvents = cmd{cmdSetVirtualMachine, 16} - cmdVirtualMachineCapabilitiesNew = cmd{cmdSetVirtualMachine, 17} - cmdVirtualMachineRedefineClasses = cmd{cmdSetVirtualMachine, 18} - cmdVirtualMachineSetDefaultStratum = cmd{cmdSetVirtualMachine, 19} - cmdVirtualMachineAllClassesWithGeneric = cmd{cmdSetVirtualMachine, 20} - - cmdReferenceTypeSignature = cmd{cmdSetReferenceType, 1} - cmdReferenceTypeClassLoader = cmd{cmdSetReferenceType, 2} - cmdReferenceTypeModifiers = cmd{cmdSetReferenceType, 3} - cmdReferenceTypeFields = cmd{cmdSetReferenceType, 4} - cmdReferenceTypeMethods = cmd{cmdSetReferenceType, 5} - cmdReferenceTypeGetValues = cmd{cmdSetReferenceType, 6} - cmdReferenceTypeSourceFile = cmd{cmdSetReferenceType, 7} - cmdReferenceTypeNestedTypes = cmd{cmdSetReferenceType, 8} - cmdReferenceTypeStatus = cmd{cmdSetReferenceType, 9} - cmdReferenceTypeInterfaces = cmd{cmdSetReferenceType, 10} - cmdReferenceTypeClassObject = cmd{cmdSetReferenceType, 11} - cmdReferenceTypeSourceDebugExtension = cmd{cmdSetReferenceType, 12} - cmdReferenceTypeSignatureWithGeneric = cmd{cmdSetReferenceType, 13} - cmdReferenceTypeFieldsWithGeneric = cmd{cmdSetReferenceType, 14} - cmdReferenceTypeMethodsWithGeneric = cmd{cmdSetReferenceType, 15} - - cmdClassTypeSuperclass = cmd{cmdSetClassType, 1} - cmdClassTypeSetValues = cmd{cmdSetClassType, 2} - cmdClassTypeInvokeMethod = cmd{cmdSetClassType, 3} - cmdClassTypeNewInstance = cmd{cmdSetClassType, 4} - - cmdArrayTypeNewInstance = cmd{cmdSetArrayType, 1} - - cmdMethodTypeLineTable = cmd{cmdSetMethod, 1} - cmdMethodTypeVariableTable = cmd{cmdSetMethod, 2} - cmdMethodTypeBytecodes = cmd{cmdSetMethod, 3} - cmdMethodTypeIsObsolete = cmd{cmdSetMethod, 4} - cmdMethodTypeVariableTableWithGeneric = cmd{cmdSetMethod, 5} - - cmdObjectReferenceReferenceType = cmd{cmdSetObjectReference, 1} - cmdObjectReferenceGetValues = cmd{cmdSetObjectReference, 2} - cmdObjectReferenceSetValues = cmd{cmdSetObjectReference, 3} - cmdObjectReferenceMonitorInfo = cmd{cmdSetObjectReference, 5} - cmdObjectReferenceInvokeMethod = cmd{cmdSetObjectReference, 6} - cmdObjectReferenceDisableCollection = cmd{cmdSetObjectReference, 7} - cmdObjectReferenceEnableCollection = cmd{cmdSetObjectReference, 8} - cmdObjectReferenceIsCollected = cmd{cmdSetObjectReference, 9} - - cmdStringReferenceValue = cmd{cmdSetStringReference, 1} - - cmdThreadReferenceName = cmd{cmdSetThreadReference, 1} - cmdThreadReferenceSuspend = cmd{cmdSetThreadReference, 2} - cmdThreadReferenceResume = cmd{cmdSetThreadReference, 3} - cmdThreadReferenceStatus = cmd{cmdSetThreadReference, 4} - cmdThreadReferenceThreadGroup = cmd{cmdSetThreadReference, 5} - cmdThreadReferenceFrames = cmd{cmdSetThreadReference, 6} - cmdThreadReferenceFrameCount = cmd{cmdSetThreadReference, 7} - cmdThreadReferenceOwnedMonitors = cmd{cmdSetThreadReference, 8} - cmdThreadReferenceCurrentContendedMonitor = cmd{cmdSetThreadReference, 9} - cmdThreadReferenceStop = cmd{cmdSetThreadReference, 10} - cmdThreadReferenceInterrupt = cmd{cmdSetThreadReference, 11} - cmdThreadReferenceSuspendCount = cmd{cmdSetThreadReference, 12} - - cmdThreadGroupReferenceName = cmd{cmdSetThreadGroupReference, 1} - cmdThreadGroupReferenceParent = cmd{cmdSetThreadGroupReference, 2} - cmdThreadGroupReferenceChildren = cmd{cmdSetThreadGroupReference, 3} - - cmdArrayReferenceLength = cmd{cmdSetArrayReference, 1} - cmdArrayReferenceGetValues = cmd{cmdSetArrayReference, 2} - cmdArrayReferenceSetValues = cmd{cmdSetArrayReference, 3} - - cmdClassLoaderReferenceVisibleClasses = cmd{cmdSetClassLoaderReference, 1} - - cmdEventRequestSet = cmd{cmdSetEventRequest, 1} - cmdEventRequestClear = cmd{cmdSetEventRequest, 2} - cmdEventRequestClearAllBreakpoints = cmd{cmdSetEventRequest, 3} - - cmdStackFrameGetValues = cmd{cmdSetStackFrame, 1} - cmdStackFrameSetValues = cmd{cmdSetStackFrame, 2} - cmdStackFrameThisObject = cmd{cmdSetStackFrame, 3} - cmdStackFramePopFrames = cmd{cmdSetStackFrame, 4} - - cmdClassObjectReferenceReflectedType = cmd{cmdSetClassObjectReference, 1} - - cmdEventComposite = cmd{cmdSetEvent, 1} -) - -var cmdNames = map[cmd]string{} - -func init() { - register := func(c cmd, n string) { - if _, e := cmdNames[c]; e { - panic("command already registered") - } - cmdNames[c] = n - } - register(cmdVirtualMachineVersion, "Version") - register(cmdVirtualMachineClassesBySignature, "ClassesBySignature") - register(cmdVirtualMachineAllClasses, "AllClasses") - register(cmdVirtualMachineAllThreads, "AllThreads") - register(cmdVirtualMachineTopLevelThreadGroups, "TopLevelThreadGroups") - register(cmdVirtualMachineDispose, "Dispose") - register(cmdVirtualMachineIDSizes, "IDSizes") - register(cmdVirtualMachineSuspend, "Suspend") - register(cmdVirtualMachineResume, "Resume") - register(cmdVirtualMachineExit, "Exit") - register(cmdVirtualMachineCreateString, "CreateString") - register(cmdVirtualMachineCapabilities, "Capabilities") - register(cmdVirtualMachineClassPaths, "ClassPaths") - register(cmdVirtualMachineDisposeObjects, "DisposeObjects") - register(cmdVirtualMachineHoldEvents, "HoldEvents") - register(cmdVirtualMachineReleaseEvents, "ReleaseEvents") - register(cmdVirtualMachineCapabilitiesNew, "CapabilitiesNew") - register(cmdVirtualMachineRedefineClasses, "RedefineClasses") - register(cmdVirtualMachineSetDefaultStratum, "SetDefaultStratum") - register(cmdVirtualMachineAllClassesWithGeneric, "AllClassesWithGeneric") - - register(cmdReferenceTypeSignature, "Signature") - register(cmdReferenceTypeClassLoader, "ClassLoader") - register(cmdReferenceTypeModifiers, "Modifiers") - register(cmdReferenceTypeFields, "Fields") - register(cmdReferenceTypeMethods, "Methods") - register(cmdReferenceTypeGetValues, "GetValues") - register(cmdReferenceTypeSourceFile, "SourceFile") - register(cmdReferenceTypeNestedTypes, "NestedTypes") - register(cmdReferenceTypeStatus, "Status") - register(cmdReferenceTypeInterfaces, "Interfaces") - register(cmdReferenceTypeClassObject, "ClassObject") - register(cmdReferenceTypeSourceDebugExtension, "SourceDebugExtension") - register(cmdReferenceTypeSignatureWithGeneric, "SignatureWithGeneric") - register(cmdReferenceTypeFieldsWithGeneric, "FieldsWithGeneric") - register(cmdReferenceTypeMethodsWithGeneric, "MethodsWithGeneric") - - register(cmdClassTypeSuperclass, "Superclass") - register(cmdClassTypeSetValues, "SetValues") - register(cmdClassTypeInvokeMethod, "InvokeMethod") - register(cmdClassTypeNewInstance, "NewInstance") - - register(cmdArrayTypeNewInstance, "NewInstance") - - register(cmdMethodTypeLineTable, "LineTable") - register(cmdMethodTypeVariableTable, "VariableTable") - register(cmdMethodTypeBytecodes, "Bytecodes") - register(cmdMethodTypeIsObsolete, "IsObsolete") - register(cmdMethodTypeVariableTableWithGeneric, "VariableTableWithGeneric") - - register(cmdObjectReferenceReferenceType, "ReferenceType") - register(cmdObjectReferenceGetValues, "GetValues") - register(cmdObjectReferenceSetValues, "SetValues") - register(cmdObjectReferenceMonitorInfo, "MonitorInfo") - register(cmdObjectReferenceInvokeMethod, "InvokeMethod") - register(cmdObjectReferenceDisableCollection, "DisableCollection") - register(cmdObjectReferenceEnableCollection, "EnableCollection") - register(cmdObjectReferenceIsCollected, "IsCollected") - - register(cmdStringReferenceValue, "Value") - - register(cmdThreadReferenceName, "Name") - register(cmdThreadReferenceSuspend, "Suspend") - register(cmdThreadReferenceResume, "Resume") - register(cmdThreadReferenceStatus, "Status") - register(cmdThreadReferenceThreadGroup, "ThreadGroup") - register(cmdThreadReferenceFrames, "Frames") - register(cmdThreadReferenceFrameCount, "FrameCount") - register(cmdThreadReferenceOwnedMonitors, "OwnedMonitors") - register(cmdThreadReferenceCurrentContendedMonitor, "CurrentContendedMonitor") - register(cmdThreadReferenceStop, "Stop") - register(cmdThreadReferenceInterrupt, "Interrupt") - register(cmdThreadReferenceSuspendCount, "SuspendCount") - - register(cmdThreadGroupReferenceName, "Name") - register(cmdThreadGroupReferenceParent, "Parent") - register(cmdThreadGroupReferenceChildren, "Children") - - register(cmdArrayReferenceLength, "Length") - register(cmdArrayReferenceGetValues, "GetValues") - register(cmdArrayReferenceSetValues, "SetValues") - - register(cmdClassLoaderReferenceVisibleClasses, "VisibleClasses") - - register(cmdEventRequestSet, "Set") - register(cmdEventRequestClear, "Clear") - register(cmdEventRequestClearAllBreakpoints, "ClearAllBreakpoints") - - register(cmdStackFrameGetValues, "GetValues") - register(cmdStackFrameSetValues, "SetValues") - register(cmdStackFrameThisObject, "ThisObject") - register(cmdStackFramePopFrames, "PopFrames") - - register(cmdClassObjectReferenceReflectedType, "ReflectedType") - - register(cmdEventComposite, "Composite") -} diff --git a/core/java/jdwp/coder.go b/core/java/jdwp/coder.go deleted file mode 100644 index ff570c0282..0000000000 --- a/core/java/jdwp/coder.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "fmt" - "reflect" - - "github.com/google/gapid/core/data/binary" -) - -// debug adds panic handlers to encode() and decode() so that incorrectly -// handled types can be more easily identified. -const debug = false - -func unbox(v reflect.Value) reflect.Value { - if v.Kind() == reflect.Interface { - return v.Elem() - } - return v -} - -// encode writes the value v to w, using the JDWP encoding scheme. -func (c *Connection) encode(w binary.Writer, v reflect.Value) error { - if debug { - defer func() { - if r := recover(); r != nil { - panic(fmt.Errorf("Type %T %v %v", v.Interface(), v.Type().Name(), v.Kind())) - } - }() - } - - t := v.Type() - o := v.Interface() - - switch v.Type() { - case reflect.TypeOf((*EventModifier)(nil)).Elem(): - // EventModifier's are prefixed with their 1-byte modKind. - w.Uint8(o.(EventModifier).modKind()) - - case reflect.TypeOf((*Value)(nil)).Elem(): - // values are prefixed with their 1-tag type. - switch o.(type) { - case ArrayID: - w.Uint8(uint8(TagArray)) - case byte: - w.Uint8(uint8(TagByte)) - case Char: - w.Uint8(uint8(TagChar)) - case ObjectID: - w.Uint8(uint8(TagObject)) - case float32: - w.Uint8(uint8(TagFloat)) - case float64: - w.Uint8(uint8(TagDouble)) - case int, int32: - w.Uint8(uint8(TagInt)) - case int16: - w.Uint8(uint8(TagShort)) - case int64: - w.Uint8(uint8(TagLong)) - case nil: - w.Uint8(uint8(TagVoid)) - case bool: - w.Uint8(uint8(TagBoolean)) - case StringID: - w.Uint8(uint8(TagString)) - case ThreadID: - w.Uint8(uint8(TagThread)) - case ThreadGroupID: - w.Uint8(uint8(TagThreadGroup)) - case ClassLoaderID: - w.Uint8(uint8(TagClassLoader)) - case ClassObjectID: - w.Uint8(uint8(TagClassObject)) - default: - panic(fmt.Errorf("Got Value of type %T", o)) - } - } - - switch o := o.(type) { - case ReferenceTypeID, ClassID, InterfaceID, ArrayTypeID: - binary.WriteUint(w, c.idSizes.ReferenceTypeIDSize*8, unbox(v).Uint()) - - case MethodID: - binary.WriteUint(w, c.idSizes.MethodIDSize*8, unbox(v).Uint()) - - case FieldID: - binary.WriteUint(w, c.idSizes.FieldIDSize*8, unbox(v).Uint()) - - case ObjectID, ThreadID, ThreadGroupID, StringID, ClassLoaderID, ClassObjectID, ArrayID: - binary.WriteUint(w, c.idSizes.ObjectIDSize*8, unbox(v).Uint()) - - case []byte: // Optimisation - w.Uint32(uint32(len(o))) - w.Data(o) - - default: - switch t.Kind() { - case reflect.Ptr, reflect.Interface: - return c.encode(w, v.Elem()) - case reflect.String: - w.Uint32(uint32(v.Len())) - w.Data([]byte(v.String())) - case reflect.Uint8: - w.Uint8(uint8(v.Uint())) - case reflect.Uint64: - w.Uint64(uint64(v.Uint())) - case reflect.Int8: - w.Int8(int8(v.Int())) - case reflect.Int16: - w.Int16(int16(v.Int())) - case reflect.Int32, reflect.Int: - w.Int32(int32(v.Int())) - case reflect.Int64: - w.Int64(v.Int()) - case reflect.Float32: - w.Float32(float32(v.Float())) - case reflect.Float64: - w.Float64(v.Float()) - case reflect.Bool: - w.Bool(v.Bool()) - case reflect.Struct: - for i, count := 0, v.NumField(); i < count; i++ { - c.encode(w, v.Field(i)) - } - case reflect.Slice: - count := v.Len() - w.Uint32(uint32(count)) - for i := 0; i < count; i++ { - c.encode(w, v.Index(i)) - } - default: - panic(fmt.Errorf("Unhandled type %T %v %v", o, t.Name(), t.Kind())) - } - } - return w.Error() -} - -// decode reads the value v from r, using the JDWP encoding scheme. -func (c *Connection) decode(r binary.Reader, v reflect.Value) error { - if debug { - defer func() { - if r := recover(); r != nil { - panic(fmt.Errorf("Type %T %v %v", v.Interface(), v.Type().Name(), v.Kind())) - } - }() - } - - switch v.Type() { - case reflect.TypeOf((*Event)(nil)).Elem(): - var kind EventKind - if err := c.decode(r, reflect.ValueOf(&kind)); err != nil { - return err - } - event := kind.event() - v.Set(reflect.ValueOf(event)) - v = v.Elem() - // Continue to decode event body below. - - case reflect.TypeOf((*Value)(nil)).Elem(): - tag := Tag(r.Uint8()) - var ty reflect.Type - switch tag { - case TagArray: - ty = reflect.TypeOf(ArrayID(0)) - case TagByte: - ty = reflect.TypeOf(byte(0)) - case TagChar: - ty = reflect.TypeOf(Char(0)) - case TagObject: - ty = reflect.TypeOf(ObjectID(0)) - case TagFloat: - ty = reflect.TypeOf(float32(0)) - case TagDouble: - ty = reflect.TypeOf(float64(0)) - case TagInt: - ty = reflect.TypeOf(int(0)) - case TagShort: - ty = reflect.TypeOf(int16(0)) - case TagLong: - ty = reflect.TypeOf(int64(0)) - case TagBoolean: - ty = reflect.TypeOf(false) - case TagString: - ty = reflect.TypeOf(StringID(0)) - case TagThread: - ty = reflect.TypeOf(ThreadID(0)) - case TagThreadGroup: - ty = reflect.TypeOf(ThreadGroupID(0)) - case TagClassLoader: - ty = reflect.TypeOf(ClassLoaderID(0)) - case TagClassObject: - ty = reflect.TypeOf(ClassObjectID(0)) - case TagVoid: - v.Set(reflect.New(v.Type()).Elem()) - return r.Error() - default: - panic(fmt.Errorf("Unhandled value type %v", tag)) - } - data := reflect.New(ty).Elem() - c.decode(r, data) - v.Set(data) - return r.Error() - } - - t := v.Type() - o := v.Interface() - switch o := o.(type) { - case ReferenceTypeID, ClassID, InterfaceID, ArrayTypeID: - v.Set(reflect.ValueOf(binary.ReadUint(r, c.idSizes.ReferenceTypeIDSize*8)).Convert(t)) - - case MethodID: - v.Set(reflect.ValueOf(binary.ReadUint(r, c.idSizes.MethodIDSize*8)).Convert(t)) - - case FieldID: - v.Set(reflect.ValueOf(binary.ReadUint(r, c.idSizes.FieldIDSize*8)).Convert(t)) - - case ObjectID, ThreadID, ThreadGroupID, StringID, ClassLoaderID, ClassObjectID, ArrayID: - v.Set(reflect.ValueOf(binary.ReadUint(r, c.idSizes.ObjectIDSize*8)).Convert(t)) - - case EventModifier: - panic("Cannot decode EventModifiers") - - default: - switch t.Kind() { - case reflect.Ptr, reflect.Interface: - return c.decode(r, v.Elem()) - case reflect.String: - data := make([]byte, r.Uint32()) - r.Data(data) - v.Set(reflect.ValueOf(string(data)).Convert(t)) - case reflect.Bool: - v.Set(reflect.ValueOf(r.Bool()).Convert(t)) - case reflect.Uint8: - v.Set(reflect.ValueOf(r.Uint8()).Convert(t)) - case reflect.Uint64: - v.Set(reflect.ValueOf(r.Uint64()).Convert(t)) - case reflect.Int8: - v.Set(reflect.ValueOf(r.Int8()).Convert(t)) - case reflect.Int16: - v.Set(reflect.ValueOf(r.Int16()).Convert(t)) - case reflect.Int32, reflect.Int: - v.Set(reflect.ValueOf(r.Int32()).Convert(t)) - case reflect.Int64: - v.Set(reflect.ValueOf(r.Int64()).Convert(t)) - case reflect.Struct: - for i, count := 0, v.NumField(); i < count; i++ { - c.decode(r, v.Field(i)) - } - case reflect.Slice: - count := int(r.Uint32()) - slice := reflect.MakeSlice(t, count, count) - for i := 0; i < count; i++ { - c.decode(r, slice.Index(i)) - } - v.Set(slice) - default: - panic(fmt.Errorf("Unhandled type %T %v %v", o, t.Name(), t.Kind())) - } - } - return r.Error() -} diff --git a/core/java/jdwp/debug.go b/core/java/jdwp/debug.go deleted file mode 100644 index 67d3ee502f..0000000000 --- a/core/java/jdwp/debug.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -func dbg(msg string, args ...interface{}) { - const enabled = false - if enabled { - fmt.Println(fmt.Sprintf(msg, args...)) - } -} diff --git a/core/java/jdwp/errors.go b/core/java/jdwp/errors.go deleted file mode 100644 index eee273c6e4..0000000000 --- a/core/java/jdwp/errors.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// Error is an enumerator of error codes returned by JDWP. -type Error uint16 - -const ( - ErrNone = Error(0) - ErrInvalidThread = Error(10) - ErrInvalidThreadGroup = Error(11) - ErrInvalidPriority = Error(12) - ErrThreadNotSuspended = Error(13) - ErrThreadSuspended = Error(14) - ErrInvalidObject = Error(20) - ErrInvalidClass = Error(21) - ErrClassNotPrepared = Error(22) - ErrInvalidMethodID = Error(23) - ErrInvalidLocation = Error(24) - ErrInvalidFieldID = Error(25) - ErrInvalidFrameID = Error(30) - ErrNoMoreFrames = Error(31) - ErrOpaqueFrame = Error(32) - ErrNotCurrentFrame = Error(33) - ErrTypeMismatch = Error(34) - ErrInvalidSlot = Error(35) - ErrDuplicate = Error(40) - ErrNotFound = Error(41) - ErrInvalidMonitor = Error(50) - ErrNotMonitorOwner = Error(51) - ErrInterrupt = Error(52) - ErrInvalidClassFormat = Error(60) - ErrCircularClassDefinition = Error(61) - ErrFailsVerification = Error(62) - ErrAddMethodNotImplemented = Error(63) - ErrSchemaChangeNotImplemented = Error(64) - ErrInvalidTypestate = Error(65) - ErrHierarchyChangeNotImplemented = Error(66) - ErrDeleteMethodNotImplemented = Error(67) - ErrUnsupportedVersion = Error(68) - ErrNamesDontMatch = Error(69) - ErrClassModifiersChangeNotImplemented = Error(70) - ErrMethodModifiersChangeNotImplemented = Error(71) - ErrNotImplemented = Error(99) - ErrNullPointer = Error(100) - ErrAbsentInformation = Error(101) - ErrInvalidEventType = Error(102) - ErrIllegalArgument = Error(103) - ErrOutOfMemory = Error(110) - ErrAccessDenied = Error(111) - ErrVMDead = Error(112) - ErrInternal = Error(113) - ErrUnattachedThread = Error(115) - ErrInvalidTag = Error(500) - ErrAlreadyInvoking = Error(502) - ErrInvalidIndex = Error(503) - ErrInvalidLength = Error(504) - ErrInvalidString = Error(506) - ErrInvalidClassLoader = Error(507) - ErrInvalidArray = Error(508) - ErrTransportLoad = Error(509) - ErrTransportInit = Error(510) - ErrNativeMethod = Error(511) - ErrInvalidCount = Error(512) -) - -func (e Error) Error() string { - switch e { - case ErrNone: - return "No error has occurred." - case ErrInvalidThread: - return "Passed thread is null, is not a valid thread or has exited." - case ErrInvalidThreadGroup: - return "Thread group invalid." - case ErrInvalidPriority: - return "Invalid priority." - case ErrThreadNotSuspended: - return "The specified thread has not been suspended by an event." - case ErrThreadSuspended: - return "Thread already suspended." - case ErrInvalidObject: - return "This reference type has been unloaded and garbage collected." - case ErrInvalidClass: - return "Invalid class." - case ErrClassNotPrepared: - return "Class has been loaded but not yet prepared." - case ErrInvalidMethodID: - return "Invalid method." - case ErrInvalidLocation: - return "Invalid location." - case ErrInvalidFieldID: - return "Invalid field." - case ErrInvalidFrameID: - return "Invalid jframeID." - case ErrNoMoreFrames: - return "There are no more Java or JNI frames on the call stack." - case ErrOpaqueFrame: - return "Information about the frame is not available." - case ErrNotCurrentFrame: - return "Operation can only be performed on current frame." - case ErrTypeMismatch: - return "The variable is not an appropriate type for the function used." - case ErrInvalidSlot: - return "Invalid slot." - case ErrDuplicate: - return "Item already set." - case ErrNotFound: - return "Desired element not found." - case ErrInvalidMonitor: - return "Invalid monitor." - case ErrNotMonitorOwner: - return "This thread doesn't own the monitor." - case ErrInterrupt: - return "The call has been interrupted before completion." - case ErrInvalidClassFormat: - return "The virtual machine attempted to read a class file and determined that the file is malformed or otherwise cannot be interpreted as a class file." - case ErrCircularClassDefinition: - return "A circularity has been detected while initializing a class." - case ErrFailsVerification: - return "The verifier detected that a class file, though well formed, contained some sort of internal inconsistency or security problem." - case ErrAddMethodNotImplemented: - return "Adding methods has not been implemented." - case ErrSchemaChangeNotImplemented: - return "Schema change has not been implemented." - case ErrInvalidTypestate: - return "The state of the thread has been modified, and is now inconsistent." - case ErrHierarchyChangeNotImplemented: - return "A direct superclass is different for the new class version, or the set of directly implemented interfaces is different and canUnrestrictedlyRedefineClasses is false." - case ErrDeleteMethodNotImplemented: - return "The new class version does not declare a method declared in the old class version and canUnrestrictedlyRedefineClasses is false." - case ErrUnsupportedVersion: - return "A class file has a version number not supported by this VM." - case ErrNamesDontMatch: - return "The class name defined in the new class file is different from the name in the old class object." - case ErrClassModifiersChangeNotImplemented: - return "The new class version has different modifiers and and canUnrestrictedlyRedefineClasses is false." - case ErrMethodModifiersChangeNotImplemented: - return "A method in the new class version has different modifiers than its counterpart in the old class version and and canUnrestrictedlyRedefineClasses is false." - case ErrNotImplemented: - return "The functionality is not implemented in this virtual machine." - case ErrNullPointer: - return "Invalid pointer." - case ErrAbsentInformation: - return "Desired information is not available." - case ErrInvalidEventType: - return "The specified event type id is not recognized." - case ErrIllegalArgument: - return "Illegal argument." - case ErrOutOfMemory: - return "The function needed to allocate memory and no more memory was available for allocation." - case ErrAccessDenied: - return "Debugging has not been enabled in this virtual machine. JVMDI cannot be used." - case ErrVMDead: - return "The virtual machine is not running." - case ErrInternal: - return "An unexpected internal error has occurred." - case ErrUnattachedThread: - return "The thread being used to call this function is not attached to the virtual machine. Calls must be made from attached threads." - case ErrInvalidTag: - return "object type id or class tag." - case ErrAlreadyInvoking: - return "Previous invoke not complete." - case ErrInvalidIndex: - return "Index is invalid." - case ErrInvalidLength: - return "The length is invalid." - case ErrInvalidString: - return "The string is invalid." - case ErrInvalidClassLoader: - return "The class loader is invalid." - case ErrInvalidArray: - return "The array is invalid." - case ErrTransportLoad: - return "Unable to load the transport." - case ErrTransportInit: - return "Unable to initialize the transport." - case ErrNativeMethod: - return "Error native method." - case ErrInvalidCount: - return "The count is invalid." - } - return fmt.Sprintf("Error<%v>", int(e)) -} diff --git a/core/java/jdwp/event.go b/core/java/jdwp/event.go deleted file mode 100644 index 92d64e6042..0000000000 --- a/core/java/jdwp/event.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// events is a collection of events. -type events struct { - Policy SuspendPolicy - Events []Event -} - -// Event is the interface implemented by all events raised by the VM. -type Event interface { - request() EventRequestID - Kind() EventKind -} - -// EventVMStart represents an event raised when the virtual machine is started. -type EventVMStart struct { - Request EventRequestID - Thread ThreadID -} - -// EventVMDeath represents an event raised when the virtual machine is stopped. -type EventVMDeath struct { - Request EventRequestID -} - -// EventSingleStep represents an event raised when a single-step has been completed. -type EventSingleStep struct { - Request EventRequestID - Thread ThreadID - Location Location -} - -// EventBreakpoint represents an event raised when a breakpoint has been hit. -type EventBreakpoint struct { - Request EventRequestID - Thread ThreadID - Location Location -} - -// EventMethodEntry represents an event raised when a method has been entered. -type EventMethodEntry struct { - Request EventRequestID - Thread ThreadID - Location Location -} - -// EventMethodExit represents an event raised when a method has been exited. -type EventMethodExit struct { - Request EventRequestID - Thread ThreadID - Location Location -} - -// EventException represents an event raised when an exception is thrown. -type EventException struct { - Request EventRequestID - Thread ThreadID - Location Location - Exception TaggedObjectID - CatchLocation Location -} - -// EventThreadStart represents an event raised when a new thread is started. -type EventThreadStart struct { - Request EventRequestID - Thread ThreadID -} - -// EventThreadDeath represents an event raised when a thread is stopped. -type EventThreadDeath struct { - Request EventRequestID - Thread ThreadID -} - -// EventClassPrepare represents an event raised when a class enters the prepared state. -type EventClassPrepare struct { - Request EventRequestID - Thread ThreadID - ClassKind TypeTag - ClassType ReferenceTypeID - Signature string - Status ClassStatus -} - -// EventClassUnload represents an event raised when a class is unloaded. -type EventClassUnload struct { - Request EventRequestID - Signature string -} - -// EventFieldAccess represents an event raised when a field is accessed. -type EventFieldAccess struct { - Request EventRequestID - Thread ThreadID - Location Location - FieldKind TypeTag - FieldType ReferenceTypeID - Field FieldID - Object TaggedObjectID -} - -// EventFieldModification represents an event raised when a field is modified. -type EventFieldModification struct { - Request EventRequestID - Thread ThreadID - Location Location - FieldKind TypeTag - FieldType ReferenceTypeID - Field FieldID - Object TaggedObjectID - NewValue Value -} - -func (e EventVMStart) request() EventRequestID { return e.Request } -func (e EventVMDeath) request() EventRequestID { return e.Request } -func (e EventSingleStep) request() EventRequestID { return e.Request } -func (e EventBreakpoint) request() EventRequestID { return e.Request } -func (e EventMethodEntry) request() EventRequestID { return e.Request } -func (e EventMethodExit) request() EventRequestID { return e.Request } -func (e EventException) request() EventRequestID { return e.Request } -func (e EventThreadStart) request() EventRequestID { return e.Request } -func (e EventThreadDeath) request() EventRequestID { return e.Request } -func (e EventClassPrepare) request() EventRequestID { return e.Request } -func (e EventClassUnload) request() EventRequestID { return e.Request } -func (e EventFieldAccess) request() EventRequestID { return e.Request } -func (e EventFieldModification) request() EventRequestID { return e.Request } - -// Kind returns VMStart -func (EventVMStart) Kind() EventKind { return VMStart } - -// Kind returns VMDeath -func (EventVMDeath) Kind() EventKind { return VMDeath } - -// Kind returns SingleStep -func (EventSingleStep) Kind() EventKind { return SingleStep } - -// Kind returns Breakpoint -func (EventBreakpoint) Kind() EventKind { return Breakpoint } - -// Kind returns MethodEntry -func (EventMethodEntry) Kind() EventKind { return MethodEntry } - -// Kind returns MethodExit -func (EventMethodExit) Kind() EventKind { return MethodExit } - -// Kind returns Exception -func (EventException) Kind() EventKind { return Exception } - -// Kind returns ThreadStart -func (EventThreadStart) Kind() EventKind { return ThreadStart } - -// Kind returns ThreadDeath -func (EventThreadDeath) Kind() EventKind { return ThreadDeath } - -// Kind returns ClassPrepare -func (EventClassPrepare) Kind() EventKind { return ClassPrepare } - -// Kind returns ClassUnload -func (EventClassUnload) Kind() EventKind { return ClassUnload } - -// Kind returns FieldAccess -func (EventFieldAccess) Kind() EventKind { return FieldAccess } - -// Kind returns FieldModification -func (EventFieldModification) Kind() EventKind { return FieldModification } diff --git a/core/java/jdwp/event_kind.go b/core/java/jdwp/event_kind.go deleted file mode 100644 index c116325cb5..0000000000 --- a/core/java/jdwp/event_kind.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// EventKind represents the type of event to set, or being raised. -type EventKind uint8 - -const ( - // SingleStep is the kind of event raised when a single-step has been completed. - SingleStep = EventKind(1) - // Breakpoint is the kind of event raised when a breakpoint has been hit. - Breakpoint = EventKind(2) - // FramePop is the kind of event raised when a stack-frame is popped. - FramePop = EventKind(3) - // Exception is the kind of event raised when an exception is thrown. - Exception = EventKind(4) - // UserDefined is the kind of event raised when a user-defind event is fired. - UserDefined = EventKind(5) - // ThreadStart is the kind of event raised when a new thread is started. - ThreadStart = EventKind(6) - // ThreadDeath is the kind of event raised when a thread is stopped. - ThreadDeath = EventKind(7) - // ClassPrepare is the kind of event raised when a class enters the prepared state. - ClassPrepare = EventKind(8) - // ClassUnload is the kind of event raised when a class is unloaded. - ClassUnload = EventKind(9) - // ClassLoad is the kind of event raised when a class enters the loaded state. - ClassLoad = EventKind(10) - // FieldAccess is the kind of event raised when a field is accessed. - FieldAccess = EventKind(20) - // FieldModification is the kind of event raised when a field is modified. - FieldModification = EventKind(21) - // ExceptionCatch is the kind of event raised when an exception is caught. - ExceptionCatch = EventKind(30) - // MethodEntry is the kind of event raised when a method has been entered. - MethodEntry = EventKind(40) - // MethodExit is the kind of event raised when a method has been exited. - MethodExit = EventKind(41) - // VMStart is the kind of event raised when the virtual machine is initialized. - VMStart = EventKind(90) - // VMDeath is the kind of event raised when the virtual machine is shutdown. - VMDeath = EventKind(99) -) - -func (k EventKind) String() string { - switch k { - case SingleStep: - return "SingleStep" - case Breakpoint: - return "Breakpoint" - case FramePop: - return "FramePop" - case Exception: - return "Exception" - case UserDefined: - return "UserDefined" - case ThreadStart: - return "ThreadStart" - case ThreadDeath: - return "ThreadDeath" - case ClassPrepare: - return "ClassPrepare" - case ClassUnload: - return "ClassUnload" - case ClassLoad: - return "ClassLoad" - case FieldAccess: - return "FieldAccess" - case FieldModification: - return "FieldModification" - case ExceptionCatch: - return "ExceptionCatch" - case MethodEntry: - return "MethodEntry" - case MethodExit: - return "MethodExit" - case VMStart: - return "VMStart" - case VMDeath: - return "VMDeath" - default: - return fmt.Sprintf("EventKind<%d>", int(k)) - } -} - -// event returns a default-initialzed Event of the specified kind. -func (k EventKind) event() Event { - switch k { - case SingleStep: - return &EventSingleStep{} - case Breakpoint: - return &EventBreakpoint{} - case Exception: - return &EventException{} - case ThreadStart: - return &EventThreadStart{} - case ThreadDeath: - return &EventThreadDeath{} - case ClassPrepare: - return &EventClassPrepare{} - case ClassUnload: - return &EventClassUnload{} - case FieldAccess: - return &EventFieldAccess{} - case FieldModification: - return &EventFieldModification{} - case ExceptionCatch: - return &EventException{} - case MethodEntry: - return &EventMethodEntry{} - case MethodExit: - return &EventMethodExit{} - case VMStart: - return &EventVMStart{} - case VMDeath: - return &EventVMDeath{} - default: - return nil - } -} diff --git a/core/java/jdwp/field.go b/core/java/jdwp/field.go deleted file mode 100644 index ebac0440fb..0000000000 --- a/core/java/jdwp/field.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "fmt" - "strings" -) - -// Field describes a single field -type Field struct { - ID FieldID - Name string - Signature string - ModBits ModBits -} - -// Fields is a collection of fields -type Fields []Field - -func (l Fields) String() string { - parts := make([]string, len(l)) - for i, m := range l { - parts[i] = fmt.Sprintf("%+v", m) - } - return strings.Join(parts, "\n") -} - -// FindByName returns the field with the matching name, or nil if no field with -// a matching name is found in l. -func (l Fields) FindByName(name string) *Field { - for _, f := range l { - if f.Name == name { - return &f - } - } - return nil -} - -// FindBySignature returns the field with the matching signature in l, or nil -// if no field with a matching signature is found in l. -func (l Fields) FindBySignature(name, sig string) *Field { - for _, f := range l { - if f.Name == name && f.Signature == sig { - return &f - } - } - return nil -} - -// FindByID returns the field with the matching identifier in l, or nil if no -// field with a matching identifier is found in l. -func (l Fields) FindByID(id FieldID) *Field { - for _, f := range l { - if f.ID == id { - return &f - } - } - return nil -} diff --git a/core/java/jdwp/helpers.go b/core/java/jdwp/helpers.go deleted file mode 100644 index 75b86a2bea..0000000000 --- a/core/java/jdwp/helpers.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "context" - "fmt" -) - -// GetClassBySignature returns the single loaded class matching the requested -// signature from the server. If there are no, or more than one class found, -// then an error is returned. -func (c *Connection) GetClassBySignature(signature string) (ClassInfo, error) { - classes, err := c.GetClassesBySignature(signature) - if err != nil { - return ClassInfo{}, err - } - if len(classes) != 1 { - err := fmt.Errorf("%d classes found with the signature '%v'", len(classes), signature) - return ClassInfo{}, err - } - return classes[0], nil -} - -// GetLocationMethodName returns the name of the method from the location. -func (c *Connection) GetLocationMethodName(l Location) (string, error) { - methods, err := c.GetMethods(ReferenceTypeID(l.Class)) - if err != nil { - return "", err - } - method := methods.FindByID(l.Method) - if method == nil { - return "", fmt.Errorf("Method not found with ID %v", l.Method) - } - return method.Name, nil -} - -// GetClassMethod looks up the method with the specified signature on class. -func (c *Connection) GetClassMethod(class ClassID, name, signature string) (Method, error) { - methods, err := c.GetMethods(ReferenceTypeID(class)) - if err != nil { - return Method{}, err - } - method := methods.FindBySignature(name, signature) - if method == nil { - return Method{}, fmt.Errorf("Method '%s%s' not found", name, signature) - } - return *method, nil -} - -// WaitForClassPrepare blocks until a class with a name that matches the pattern -// is prepared, and then returns the thread that prepared the class. -// All threads are suspended when the method returns. -func (c *Connection) WaitForClassPrepare(ctx context.Context, pattern string) (ThreadID, error) { - var out ThreadID - - onEvent := func(event Event) bool { - out = event.(*EventClassPrepare).Thread - return false - } - - err := c.WatchEvents(ctx, ClassPrepare, SuspendAll, onEvent, ClassMatchEventModifier(pattern)) - if err != nil { - return 0, err - } - - return out, nil -} - -// WaitForMethodEntry blocks until the method on class is entered, and then -// returns the method entry event. -// All threads are suspended when the method returns. -func (c *Connection) WaitForMethodEntry(ctx context.Context, class ClassID, method MethodID) (*EventMethodEntry, error) { - var out *EventMethodEntry - - onEvent := func(event Event) bool { - e := event.(*EventMethodEntry) - if e.Location.Method == method { - out = e - return false - } - c.ResumeAll() - return true - } - - err := c.WatchEvents(ctx, MethodEntry, SuspendAll, onEvent, ClassOnlyEventModifier(class)) - if err != nil { - return nil, err - } - - return out, nil -} diff --git a/core/java/jdwp/invoke_options.go b/core/java/jdwp/invoke_options.go deleted file mode 100644 index 153a97420b..0000000000 --- a/core/java/jdwp/invoke_options.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "strings" - -// InvokeOptions is a collection of bit flags controlling an invoke. -type InvokeOptions int - -const ( - // InvokeSingleThreaded prevents the resume of all other threads when - // performing the invoke. Once the invoke has finished, the single thread will - // suspended again. - InvokeSingleThreaded = InvokeOptions(1) - - // InvokeNonvirtual invokes the method without using regular, virtual - // invocation. - InvokeNonvirtual = InvokeOptions(2) -) - -func (i InvokeOptions) String() string { - parts := []string{} - if i&InvokeSingleThreaded != 0 { - parts = append(parts, "InvokeSingleThreaded") - } - if i&InvokeNonvirtual != 0 { - parts = append(parts, "InvokeNonvirtual") - } - return strings.Join(parts, ", ") -} diff --git a/core/java/jdwp/jdwp.go b/core/java/jdwp/jdwp.go deleted file mode 100644 index 37c0c960e6..0000000000 --- a/core/java/jdwp/jdwp.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// Package jdwp implements types to communicate using the the Java Debug Wire Protocol. -package jdwp - -import ( - "bufio" - "bytes" - "context" - "fmt" - "io" - "reflect" - "sync" - "time" - - "github.com/google/gapid/core/app/crash" - "github.com/google/gapid/core/data/binary" - "github.com/google/gapid/core/data/endian" - "github.com/google/gapid/core/os/device" -) - -var ( - handshake = []byte("JDWP-Handshake") - - defaultIDSizes = IDSizes{ - FieldIDSize: 8, - MethodIDSize: 8, - ObjectIDSize: 8, - ReferenceTypeIDSize: 8, - FrameIDSize: 8, - } -) - -// Connection represents a JDWP connection. -type Connection struct { - in io.Reader - r binary.Reader - w binary.Writer - flush func() error - idSizes IDSizes - nextPacketID packetID - events map[EventRequestID]chan<- Event - replies map[packetID]chan<- replyPacket - sync.Mutex -} - -// Open creates a Connection using conn for I/O. -func Open(ctx context.Context, conn io.ReadWriteCloser) (*Connection, error) { - if err := exchangeHandshakes(conn); err != nil { - return nil, err - } - - buf := bufio.NewWriterSize(conn, 1024) - r := endian.Reader(conn, device.BigEndian) - w := endian.Writer(buf, device.BigEndian) - c := &Connection{ - in: conn, - r: r, - w: w, - flush: buf.Flush, - idSizes: defaultIDSizes, - events: map[EventRequestID]chan<- Event{}, - replies: map[packetID]chan<- replyPacket{}, - } - - crash.Go(func() { c.recv(ctx) }) - var err error - c.idSizes, err = c.GetIDSizes() - if err != nil { - return nil, err - } - return c, nil -} - -func exchangeHandshakes(conn io.ReadWriter) error { - if _, err := conn.Write(handshake); err != nil { - return err - } - ok, err := expect(conn, handshake) - if err != nil { - return err - } - if !ok { - return fmt.Errorf("Bad handshake") - } - return nil -} - -// expect reads c.in, expecting the specfified sequence of bytes. If the read -// data doesn't match, then the function returns immediately with false. -func expect(conn io.Reader, expected []byte) (bool, error) { - got := make([]byte, len(expected)) - for len(expected) > 0 { - n, err := conn.Read(got) - if err != nil { - return false, err - } - for i := 0; i < n; i++ { - if got[i] != expected[i] { - return false, nil - } - } - got, expected = got[n:], expected[n:] - } - return true, nil -} - -// get sends the specified command and waits for a reply. -func (c *Connection) get(cmd cmd, req interface{}, out interface{}) error { - p, err := c.req(cmd, req) - if err != nil { - return err - } - return p.wait(out) -} - -// req sends the specified command and returns a pending. -func (c *Connection) req(cmd cmd, req interface{}) (*pending, error) { - data := bytes.Buffer{} - if req != nil { - e := endian.Writer(&data, device.BigEndian) - if err := c.encode(e, reflect.ValueOf(req)); err != nil { - return nil, err - } - } - - id, replyChan := c.newReplyHandler() - - p := cmdPacket{id: id, cmdSet: cmd.set, cmdID: cmd.id, data: data.Bytes()} - - c.Lock() - defer c.Unlock() - - if err := p.write(c.w); err != nil { - return nil, err - } - if err := c.flush(); err != nil { - return nil, err - } - - dbg("<%v> send: %v, %+v", id, cmd, req) - - return &pending{c, replyChan, id}, nil -} - -type pending struct { - c *Connection - p <-chan replyPacket - id packetID -} - -// wait blocks until the penging response is received, filling out with the -// response data. -func (p *pending) wait(out interface{}) error { - select { - case reply := <-p.p: - if reply.err != ErrNone { - dbg("<%v> recv err: %+v", p.id, reply.err) - return reply.err - } - if out == nil { - return nil - } - r := bytes.NewReader(reply.data) - d := endian.Reader(r, device.BigEndian) - if err := p.c.decode(d, reflect.ValueOf(out)); err != nil { - return err - } - dbg("<%v> recv: %+v", p.id, out) - if offset, _ := r.Seek(0, 1); offset != int64(len(reply.data)) { - panic(fmt.Errorf("Only %d/%d bytes read from reply packet", offset, len(reply.data))) - } - return nil - case <-time.After(time.Second * 120): - return fmt.Errorf("timeout") - } -} - -func (c *Connection) newReplyHandler() (packetID, <-chan replyPacket) { - reply := make(chan replyPacket, 1) - c.Lock() - id := c.nextPacketID - c.nextPacketID++ - c.replies[id] = reply - c.Unlock() - return id, reply -} diff --git a/core/java/jdwp/jdwp_test.go b/core/java/jdwp/jdwp_test.go deleted file mode 100644 index 363bd96165..0000000000 --- a/core/java/jdwp/jdwp_test.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp_test - -import ( - "context" - "os" - "testing" - - "github.com/google/gapid/core/assert" - "github.com/google/gapid/core/java/jdwp" - "github.com/google/gapid/core/java/jdwp/test" - "github.com/google/gapid/core/log" -) - -var source = map[string]string{ - "main.java": ` -public class main { - static { - Calculator.register(); - } - public static void main(String[] args) throws Exception { - System.out.print("Doing stuff\n"); - Thread.sleep(1000); - } -}`, - "Calculator.java": ` -public class Calculator { - public static void register() {} - - private int value = 0; - - public static int Add(int a, int b) { return a + b; } - public void Add(int a) { value += a; } - - public int Result() { return value; } -} -`, -} - -func TestMain(m *testing.M) { - ctx := context.Background() - ctx = log.PutHandler(ctx, log.Normal.Handler(log.Std())) - os.Exit(test.BuildRunAndConnect(ctx, source, func(ctx context.Context, c *jdwp.Connection) int { - // Wait for java to load the prepare class - t, err := c.WaitForClassPrepare(ctx, "Calculator") - if err != nil { - log.F(ctx, true, "Failed to wait for Calculator prepare. Error: %v", err) - return -1 - } - - connection, thread = c, t - return m.Run() - })) -} - -var connection *jdwp.Connection -var thread jdwp.ThreadID - -func TestGetClassBySignature(t *testing.T) { - ctx := log.Testing(t) - - calculator, err := connection.GetClassBySignature("LCalculator;") - if err != nil { - log.F(ctx, true, "GetClassesBySignature returned error: %v", err) - } - - assert.For(ctx, "Calculator kind").That(calculator.Kind).Equals(jdwp.Class) - assert.For(ctx, "Calculator kind").That(calculator.Signature).Equals("LCalculator;") -} - -func TestInvokeStaticMethod(t *testing.T) { - ctx := log.Testing(t) - - class, err := connection.GetClassBySignature("LCalculator;") - if err != nil { - log.F(ctx, true, "GetClassesBySignature returned error: %v", err) - return - } - - methods, err := connection.GetMethods(class.TypeID) - if err != nil { - log.F(ctx, true, "GetMethods returned error: %v", err) - return - } - - add := methods.FindBySignature("Add", "(II)I") - if add == nil { - log.F(ctx, true, "Couldn't find method Add: %v", err) - return - } - - result, err := connection.InvokeStaticMethod(class.ClassID(), add.ID, thread, jdwp.InvokeSingleThreaded, 3, 7) - if err != nil { - log.F(ctx, true, "InvokeStaticMethod returned error: %v", err) - return - } - assert.For(ctx, "Add(3, 7)").That(result.Result).Equals(10) -} - -func TestInvokeMethod(t *testing.T) { - ctx := log.Testing(t) - - class, err := connection.GetClassBySignature("LCalculator;") - if err != nil { - log.F(ctx, true, "GetClassesBySignature returned error: %v", err) - return - } - - methods, err := connection.GetMethods(class.TypeID) - if err != nil { - log.F(ctx, true, "GetMethods returned error: %v", err) - return - } - - constructor := methods.FindBySignature("", "()V") - if constructor == nil { - log.F(ctx, true, "Couldn't find constructor: %v", err) - log.I(ctx, "Available methods:\n%v", methods) - return - } - - instance, err := connection.NewInstance(class.ClassID(), constructor.ID, thread, jdwp.InvokeSingleThreaded) - if err != nil { - log.F(ctx, true, "NewInstance returned error: %v", err) - return - } - - add := methods.FindBySignature("Add", "(I)V") - if add == nil { - log.F(ctx, true, "Couldn't find method Add: %v", err) - log.I(ctx, "Available methods:\n%v", methods) - return - } - - for _, i := range []int{3, 6, 8} { - _, err = connection.InvokeMethod( - instance.Result.Object, - class.ClassID(), - add.ID, - thread, - jdwp.InvokeSingleThreaded, - i) - if err != nil { - log.F(ctx, true, "InvokeMethod returned error: %v", err) - return - } - } - - resultf := methods.FindBySignature("Result", "()I") - if resultf == nil { - log.F(ctx, true, "Couldn't find method Result: %v", err) - log.I(ctx, "Available methods:\n%v", methods) - return - } - - result, err := connection.InvokeMethod( - instance.Result.Object, - class.ClassID(), - resultf.ID, - thread, - jdwp.InvokeSingleThreaded) - if err != nil { - log.F(ctx, true, "InvokeMethod returned error: %v", err) - return - } - assert.For(ctx, "Result").That(result.Result).Equals(3 + 6 + 8) -} diff --git a/core/java/jdwp/method.go b/core/java/jdwp/method.go deleted file mode 100644 index 40b0788e6d..0000000000 --- a/core/java/jdwp/method.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "fmt" - "strings" -) - -// Method describes a single method -type Method struct { - ID MethodID - Name string - Signature string - ModBits ModBits -} - -// Methods is a collection of methods -type Methods []Method - -func (l Methods) String() string { - parts := make([]string, len(l)) - for i, m := range l { - parts[i] = fmt.Sprintf("%+v", m) - } - return strings.Join(parts, "\n") -} - -// FindBySignature returns the method with the matching signature in l, or nil -// if no method with a matching signature is found in l. -func (l Methods) FindBySignature(name, sig string) *Method { - for _, m := range l { - if m.Name == name && m.Signature == sig { - return &m - } - } - return nil -} - -// FindByID returns the method with the matching identifier in l, or nil if no -// method with a matching identifier is found in l. -func (l Methods) FindByID(id MethodID) *Method { - for _, m := range l { - if m.ID == id { - return &m - } - } - return nil -} diff --git a/core/java/jdwp/modbits.go b/core/java/jdwp/modbits.go deleted file mode 100644 index b6b07f2002..0000000000 --- a/core/java/jdwp/modbits.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "strings" - -// ModBits represents the modifier bitflags for a class, method or field. -type ModBits int - -const ( - ModPublic = ModBits(1) - ModPrivate = ModBits(2) - ModProtected = ModBits(4) - ModStatic = ModBits(8) - ModFinal = ModBits(16) - ModSynchronized = ModBits(32) - ModVolatile = ModBits(64) - ModTransient = ModBits(128) - ModInterface = ModBits(512) - ModNative = ModBits(256) - ModAbstract = ModBits(1024) - ModStrict = ModBits(2048) -) - -func (m ModBits) String() string { - parts := []string{} - if m&ModPublic != 0 { - parts = append(parts, "public") - } - if m&ModPrivate != 0 { - parts = append(parts, "private") - } - if m&ModProtected != 0 { - parts = append(parts, "protected") - } - if m&ModStatic != 0 { - parts = append(parts, "static") - } - if m&ModFinal != 0 { - parts = append(parts, "final") - } - if m&ModSynchronized != 0 { - parts = append(parts, "synchronized") - } - if m&ModVolatile != 0 { - parts = append(parts, "volatile") - } - if m&ModTransient != 0 { - parts = append(parts, "transient") - } - if m&ModInterface != 0 { - parts = append(parts, "interface") - } - if m&ModNative != 0 { - parts = append(parts, "native") - } - if m&ModAbstract != 0 { - parts = append(parts, "abstract") - } - if m&ModStrict != 0 { - parts = append(parts, "strict") - } - - return strings.Join(parts, " ") -} - -func (m ModBits) Public() bool { return m&ModPublic != 0 } -func (m ModBits) Private() bool { return m&ModPrivate != 0 } -func (m ModBits) Protected() bool { return m&ModProtected != 0 } -func (m ModBits) Static() bool { return m&ModStatic != 0 } -func (m ModBits) Final() bool { return m&ModFinal != 0 } -func (m ModBits) Synchronized() bool { return m&ModSynchronized != 0 } -func (m ModBits) Volatile() bool { return m&ModVolatile != 0 } -func (m ModBits) Transient() bool { return m&ModTransient != 0 } -func (m ModBits) Interface() bool { return m&ModInterface != 0 } -func (m ModBits) Native() bool { return m&ModNative != 0 } -func (m ModBits) Abstract() bool { return m&ModAbstract != 0 } -func (m ModBits) Strict() bool { return m&ModStrict != 0 } diff --git a/core/java/jdwp/packet.go b/core/java/jdwp/packet.go deleted file mode 100644 index c3e85a7046..0000000000 --- a/core/java/jdwp/packet.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "fmt" - - "github.com/google/gapid/core/data/binary" -) - -type packetID uint32 - -type packetFlags uint8 - -const packetIsReply = packetFlags(0x80) - -type cmdPacket struct { - id packetID - flags packetFlags - cmdSet cmdSet - cmdID cmdID - data []byte -} - -// JDWP uses the following structs for all communication: -// -// struct cmdPacket { -// length uint32 4 bytes -// id packetID 4 bytes -// flags packetFlags 1 bytes -// cmdSet cmdSet 1 bytes -// cmd uint8 1 bytes -// data []byte N bytes -// } -// -// struct reply { -// length uint32 4 bytes -// id packetID 4 bytes -// flags packetFlags 1 bytes -// err errorCode 2 bytes -// data []byte N bytes -// } - -func (p cmdPacket) write(w binary.Writer) error { - w.Uint32(11 + uint32(len(p.data))) - w.Uint32(uint32(p.id)) - w.Uint8(uint8(p.flags)) - w.Uint8(uint8(p.cmdSet)) - w.Uint8(uint8(p.cmdID)) - w.Data(p.data) - return w.Error() -} - -type replyPacket struct { - id packetID - err Error - data []byte -} - -func (c *Connection) readPacket() (interface{}, error) { - len := c.r.Uint32() - if err := c.r.Error(); err != nil { - return nil, err - } - if len < 11 { - return replyPacket{}, fmt.Errorf("Packet length too short (%d)", len) - } - id := packetID(c.r.Uint32()) - flags := packetFlags(c.r.Uint8()) - if flags&packetIsReply != 0 { - // Reply packet - out := replyPacket{ - id: id, - err: Error(c.r.Uint16()), - } - out.data = make([]byte, len-11) - c.r.Data(out.data) - return out, c.r.Error() - } - // Command packet - out := cmdPacket{ - cmdSet: cmdSet(c.r.Uint8()), - cmdID: cmdID(c.r.Uint8()), - } - out.data = make([]byte, len-11) - c.r.Data(out.data) - return out, c.r.Error() -} diff --git a/core/java/jdwp/recv.go b/core/java/jdwp/recv.go deleted file mode 100644 index c3101dfbb2..0000000000 --- a/core/java/jdwp/recv.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "bytes" - "context" - "io" - "reflect" - - "github.com/google/gapid/core/data/endian" - "github.com/google/gapid/core/event/task" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/os/device" -) - -// recv decodes all the incoming reply or command packets, forwarding them on -// to the corresponding chans. recv is blocking and should be run on a new -// go routine. -// recv returns when ctx is stopped or there's an IO error. -func (c *Connection) recv(ctx context.Context) { - for !task.Stopped(ctx) { - packet, err := c.readPacket() - switch err { - case nil: - case io.EOF: - return - default: - if !task.Stopped(ctx) { - log.W(ctx, "Failed to read packet. Error: %v", err) - } - return - } - - switch packet := packet.(type) { - case replyPacket: - c.Lock() - out, ok := c.replies[packet.id] - delete(c.replies, packet.id) - c.Unlock() - if !ok { - log.W(ctx, "Unexpected reply for packet %d", packet.id) - continue - } - out <- packet - - case cmdPacket: - switch { - case packet.cmdSet == cmdSetEvent && packet.cmdID == cmdCompositeEvent: - d := endian.Reader(bytes.NewReader(packet.data), device.BigEndian) - l := events{} - if err := c.decode(d, reflect.ValueOf(&l)); err != nil { - log.F(ctx, true, "Couldn't decode composite event data. Error: %v", err) - continue - } - - for _, ev := range l.Events { - dbg("<%v> event: %T %+v", ev.request(), ev, ev) - - c.Lock() - handler, ok := c.events[ev.request()] - c.Unlock() - - if ok { - handler <- ev - } else { - dbg("No event handler registered for %+v", ev) - } - } - - default: - dbg("received unknown packet %+v", packet) - // Unknown packet. Ignore. - } - } - } -} diff --git a/core/java/jdwp/suspend_policy.go b/core/java/jdwp/suspend_policy.go deleted file mode 100644 index a0de4aa3c0..0000000000 --- a/core/java/jdwp/suspend_policy.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// SuspendPolicy describes what threads should be suspended on an event being -// raised. -type SuspendPolicy byte - -const ( - // SuspendNone suspends no threads when a event is raised. - SuspendNone = SuspendPolicy(0) - // SuspendEventThread suspends only the event's thread when a event is raised. - SuspendEventThread = SuspendPolicy(1) - // SuspendAll suspends all threads when a event is raised. - SuspendAll = SuspendPolicy(2) -) - -func (s SuspendPolicy) String() string { - switch s { - case SuspendNone: - return "SuspendNone" - case SuspendEventThread: - return "SuspendEventThread" - case SuspendAll: - return "SuspendAll" - } - return fmt.Sprint(int(s)) -} diff --git a/core/java/jdwp/tag.go b/core/java/jdwp/tag.go deleted file mode 100644 index f3da19eff1..0000000000 --- a/core/java/jdwp/tag.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// Tag is a type kind enumerator. -type Tag uint8 - -const ( - TagArray = Tag(91) // '[' - an array object (objectID size). - TagByte = Tag(66) // 'B' - a byte value (1 byte). - TagChar = Tag(67) // 'C' - a character value (2 bytes). - TagObject = Tag(76) // 'L' - an object (objectID size). - TagFloat = Tag(70) // 'F' - a float value (4 bytes). - TagDouble = Tag(68) // 'D' - a double value (8 bytes). - TagInt = Tag(73) // 'I' - an int value (4 bytes). - TagLong = Tag(74) // 'J' - a long value (8 bytes). - TagShort = Tag(83) // 'S' - a short value (2 bytes). - TagVoid = Tag(86) // 'V' - a void value (no bytes). - TagBoolean = Tag(90) // 'Z' - a boolean value (1 byte). - TagString = Tag(115) // 's' - a String object (objectID size). - TagThread = Tag(116) // 't' - a Thread object (objectID size). - TagThreadGroup = Tag(103) // 'g' - a ThreadGroup object (objectID size). - TagClassLoader = Tag(108) // 'l' - a ClassLoader object (objectID size). - TagClassObject = Tag(99) // 'c' - a class object object (objectID size). -) - -func (t Tag) String() string { - switch t { - case TagArray: - return "Array" - case TagByte: - return "Byte" - case TagChar: - return "Char" - case TagObject: - return "Object" - case TagFloat: - return "Float" - case TagDouble: - return "Double" - case TagInt: - return "Int" - case TagLong: - return "Long" - case TagShort: - return "Short" - case TagVoid: - return "Void" - case TagBoolean: - return "Boolean" - case TagString: - return "String" - case TagThread: - return "Thread" - case TagThreadGroup: - return "ThreadGroup" - case TagClassLoader: - return "ClassLoader" - case TagClassObject: - return "ClassObject" - default: - return fmt.Sprintf("Tag<%v>", int(t)) - } -} diff --git a/core/java/jdwp/test/BUILD.bazel b/core/java/jdwp/test/BUILD.bazel deleted file mode 100644 index 7ab542dcae..0000000000 --- a/core/java/jdwp/test/BUILD.bazel +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = ["test.go"], - importpath = "github.com/google/gapid/core/java/jdwp/test", - tags = ["integration"], - visibility = ["//visibility:public"], - deps = [ - "//core/event/task:go_default_library", - "//core/java/jdwp:go_default_library", - "//core/log:go_default_library", - "//core/os/shell:go_default_library", - ], -) diff --git a/core/java/jdwp/test/test.go b/core/java/jdwp/test/test.go deleted file mode 100644 index 4250590d33..0000000000 --- a/core/java/jdwp/test/test.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package test - -import ( - "context" - "fmt" - "io" - "io/ioutil" - "net" - "os" - "path/filepath" - "time" - - "github.com/google/gapid/core/event/task" - "github.com/google/gapid/core/java/jdwp" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/os/shell" -) - -// findFreePort returns a currently free TCP port on the localhost. -// There are two potential issues with this function: -// * There is the potential for the port to be taken between the function -// returning and the port actually being used. -// * The system _may_ hold on to the socket after it has been told to close. -// Because of these issues, there is a potential for flakiness. -func findFreePort() (int, error) { - dummy, err := net.Listen("tcp", ":0") - if err != nil { - return 0, err - } - defer dummy.Close() - return dummy.Addr().(*net.TCPAddr).Port, nil -} - -// JavaSource is a map of file name 'foo.java' to the source code. -type JavaSource map[string]string - -func runJavaServer(ctx context.Context, sources JavaSource, port int) task.Signal { - signal, done := task.NewSignal() - go func() { - defer done(ctx) - tmp, err := ioutil.TempDir("", "jdwp") - if err != nil { - log.E(ctx, "Couldn't get temporary directory. Error: %v", err) - return - } - defer os.RemoveAll(tmp) - sourceNames := make([]string, 0, len(sources)) - for name, source := range sources { - if err := ioutil.WriteFile(filepath.Join(tmp, name), []byte(source), 0777); err != nil { - log.E(ctx, "Couldn't write to temporary source file. Error: %v", err) - return - } - sourceNames = append(sourceNames, name) - } - l := log.From(ctx) - if err := shell. - Command("javac", sourceNames...). - In(tmp). - Capture(nil, l.Writer(log.Error)). - Run(ctx); err != nil { - - log.E(ctx, "Couldn't compile java source. Error: %v", err) - return - } - agentlib := fmt.Sprintf("-agentlib:jdwp=transport=dt_socket,suspend=y,server=y,address=%v", port) - if err := shell. - Command("java", agentlib, "main"). - In(tmp). - Capture(l.Writer(log.Info), l.Writer(log.Error)). - Run(ctx); err != nil { - - if !task.Stopped(ctx) { - log.E(ctx, "Couldn't start java server. Error: %v", err) - } - return - } - }() - return signal -} - -// BuildRunAndConnect builds all the source files with javac, executes them with -// java, attaches to it via JDWP, then calls onConnect with the connection. -func BuildRunAndConnect(ctx context.Context, source JavaSource, onConnect func(ctx context.Context, conn *jdwp.Connection) int) int { - ctx, stop := task.WithTimeout(ctx, time.Second*30) - - port, err := findFreePort() - if err != nil { - log.E(ctx, "Failed to find a free port. Error: %v", err) - return -1 - } - - done := runJavaServer(ctx, source, port) - defer func() { - stop() - <-done - }() - - var socket io.ReadWriteCloser - for i := 0; i < 5; i++ { - var err error - if socket, err = net.Dial("tcp", fmt.Sprintf("localhost:%v", port)); err == nil { - break - } - time.Sleep(time.Second) - } - - if socket == nil { - log.E(ctx, "Failed to connect to the socket. Error: %v", err) - return -1 - } - - conn, err := jdwp.Open(ctx, socket) - if err != nil { - log.E(ctx, "Failed to open the JDWP connection. Error: %v", err) - return -1 - } - - return onConnect(ctx, conn) -} diff --git a/core/java/jdwp/thread_status.go b/core/java/jdwp/thread_status.go deleted file mode 100644 index d69b209a9b..0000000000 --- a/core/java/jdwp/thread_status.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// ThreadStatus is an enumerator of thread state. -type ThreadStatus int - -const ( - ThreadZombie = ThreadStatus(0) - ThreadRunning = ThreadStatus(1) - ThreadSleeping = ThreadStatus(2) - ThreadMonitor = ThreadStatus(3) - ThreadWait = ThreadStatus(4) -) - -// SuspendStatus is an enumerator of thread suspend state. -type SuspendStatus int - -const ( - NotSuspended = SuspendStatus(0) - Suspended = SuspendStatus(1) -) - -func (s ThreadStatus) String() string { - switch s { - case ThreadZombie: - return "Zombie" - case ThreadRunning: - return "Running" - case ThreadSleeping: - return "Sleeping" - case ThreadMonitor: - return "Monitor" - case ThreadWait: - return "Wait" - } - return fmt.Sprint(int(s)) -} - -func (s SuspendStatus) String() string { - switch s { - case NotSuspended: - return "NotSuspended" - case Suspended: - return "Suspended" - } - return fmt.Sprint(int(s)) -} diff --git a/core/java/jdwp/type_tag.go b/core/java/jdwp/type_tag.go deleted file mode 100644 index 281e650f9b..0000000000 --- a/core/java/jdwp/type_tag.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import "fmt" - -// TypeTag is an enumerator of class, interface or array. -type TypeTag uint8 - -const ( - Class = TypeTag(1) // Type is a class. - Interface = TypeTag(2) // Type is an interface. - Array = TypeTag(3) // Type is an array. -) - -func (t TypeTag) String() string { - switch t { - case Class: - return "Class" - case Interface: - return "Interface" - case Array: - return "Array" - default: - return fmt.Sprintf("TypeTag<%v>", int(t)) - } -} diff --git a/core/java/jdwp/types.go b/core/java/jdwp/types.go deleted file mode 100644 index a34691904f..0000000000 --- a/core/java/jdwp/types.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -import ( - "fmt" - "sort" -) - -// TaggedObjectID is a type and object identifier pair. -type TaggedObjectID struct { - Type Tag - Object ObjectID -} - -// Location describes a code location. -type Location struct { - Type TypeTag - Class ClassID - Method MethodID - Location uint64 -} - -// Char is a 16-bit character type. -type Char int16 - -// ObjectID is an object instance identifier. -// If the specific object type is known, then ObjectID can be cast to -// ThreadID, ThreadGroupID, StringID, ClassLoaderID, ClassObjectID or ArrayID. -type ObjectID uint64 - -// ThreadID is an thread instance identifier. -// ThreadID can always be safely cast to the less specific ObjectID. -type ThreadID uint64 - -// ThreadGroupID is an thread group identifier. -// ThreadGroupID can always be safely cast to the less specific ObjectID. -type ThreadGroupID uint64 - -// StringID is a string instance identifier. -// StringID can always be safely cast to the less specific ObjectID. -type StringID uint64 - -// ClassLoaderID is class loader identifier. -// ClassLoaderID can always be safely cast to the less specific ObjectID. -type ClassLoaderID uint64 - -// ClassObjectID is a class object instance identifier. -// ClassObjectID can always be safely cast to the less specific ObjectID. -type ClassObjectID uint64 - -// ArrayID is an array instance identifier. -// ArrayID can always be safely cast to the less specific ObjectID. -type ArrayID uint64 - -// Object is the interface implemented by all types that are a variant of ObjectID. -type Object interface { - ID() ObjectID -} - -// ID returns the ObjectID -func (i ObjectID) ID() ObjectID { return i } - -// ID returns the ThreadID as an ObjectID -func (i ThreadID) ID() ObjectID { return ObjectID(i) } - -// ID returns the ThreadGroupID as an ObjectID -func (i ThreadGroupID) ID() ObjectID { return ObjectID(i) } - -// ID returns the StringID as an ObjectID -func (i StringID) ID() ObjectID { return ObjectID(i) } - -// ID returns the ClassLoaderID as an ObjectID -func (i ClassLoaderID) ID() ObjectID { return ObjectID(i) } - -// ID returns the ClassObjectID as an ObjectID -func (i ClassObjectID) ID() ObjectID { return ObjectID(i) } - -// ID returns the ArrayID as an ObjectID -func (i ArrayID) ID() ObjectID { return ObjectID(i) } - -// ID returns the ObjectID of the TaggedObjectID -func (i TaggedObjectID) ID() ObjectID { return i.Object } - -// ReferenceTypeID is a reference type identifier. -// If the specific reference type is known, then ReferenceTypeID can be cast to -// ClassID, InterfaceID or ArrayTypeID. -type ReferenceTypeID uint64 - -// ClassID is a class reference type identifier. -// ClassID can always be safely cast to the less specific ReferenceTypeID. -type ClassID uint64 - -// InterfaceID is an interface reference type identifier. -// InterfaceID can always be safely cast to the less specific ReferenceTypeID. -type InterfaceID uint64 - -// ArrayTypeID is an array reference type identifier. -// ArrayTypeID can always be safely cast to the less specific ReferenceTypeID. -type ArrayTypeID uint64 - -// MethodID is the identifier for a single method for a class or interface. -type MethodID uint64 - -// FieldID is the identifier for a single method for a class or interface. -type FieldID uint64 - -// FrameID is the identifier for a stack frame. -type FrameID uint64 - -// FrameVariable contains all of the information a single variable. -type FrameVariable struct { - CodeIndex uint64 - Name string - Signature string - Length int - Slot int -} - -// VariableTable contains all of the variables for a stack frame. -type VariableTable struct { - ArgCount int - Slots []FrameVariable -} - -func (i ObjectID) String() string { return fmt.Sprintf("ObjectID<%d>", uint64(i)) } -func (i ThreadID) String() string { return fmt.Sprintf("ThreadID<%d>", uint64(i)) } -func (i ThreadGroupID) String() string { return fmt.Sprintf("ThreadGroupID<%d>", uint64(i)) } -func (i StringID) String() string { return fmt.Sprintf("StringID<%d>", uint64(i)) } -func (i ClassLoaderID) String() string { return fmt.Sprintf("ClassLoaderID<%d>", uint64(i)) } -func (i ClassObjectID) String() string { return fmt.Sprintf("ClassObjectID<%d>", uint64(i)) } -func (i ArrayID) String() string { return fmt.Sprintf("ArrayID<%d>", uint64(i)) } -func (i ReferenceTypeID) String() string { return fmt.Sprintf("ReferenceTypeID<%d>", uint64(i)) } -func (i ClassID) String() string { return fmt.Sprintf("ClassID<%d>", uint64(i)) } -func (i InterfaceID) String() string { return fmt.Sprintf("InterfaceID<%d>", uint64(i)) } -func (i ArrayTypeID) String() string { return fmt.Sprintf("ArrayTypeID<%d>", uint64(i)) } -func (i MethodID) String() string { return fmt.Sprintf("MethodID<%d>", uint64(i)) } -func (i FieldID) String() string { return fmt.Sprintf("FieldID<%d>", uint64(i)) } -func (i FrameID) String() string { return fmt.Sprintf("FrameID<%d>", uint64(i)) } - -// ArgumentSlots returns the slots that could possibly be method arguments. -// Slots that could be method arguments are slots that are acessible at -// location 0 and have a length > 0. Returns the result sorted by slot index. -func (v *VariableTable) ArgumentSlots() []FrameVariable { - r := []FrameVariable{} - for _, slot := range v.Slots { - if slot.CodeIndex == 0 && slot.Length > 0 { - r = append(r, slot) - } - } - sort.Slice(r, func(i, j int) bool { - return r[i].Slot < r[j].Slot - }) - return r -} diff --git a/core/java/jdwp/value.go b/core/java/jdwp/value.go deleted file mode 100644 index 76f4e87aec..0000000000 --- a/core/java/jdwp/value.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package jdwp - -// Value is a generic value that can be one of the following types: -// • bool • Char • int • int8 -// • int16 • int32 • int64 • float32 -// • float64 • ArrayID • ClassLoaderID • ClassObjectID -// • ObjectID • StringID • ThreadGroupID • ThreadID -// • nil -type Value interface{} - -// ValueSlice contains a set of values -type ValueSlice []Value - -// untaggedValue can hold the same types as Value, but when encoded / decoded it -// is not prefixed with a type tag. -type untaggedValue interface{} diff --git a/core/langsvr/BUILD.bazel b/core/langsvr/BUILD.bazel index bf6ba8b9c4..ef9904d5ac 100644 --- a/core/langsvr/BUILD.bazel +++ b/core/langsvr/BUILD.bazel @@ -48,6 +48,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["body_test.go"], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/log/BUILD.bazel b/core/log/BUILD.bazel index 647822ea42..892a35aa78 100644 --- a/core/log/BUILD.bazel +++ b/core/log/BUILD.bazel @@ -57,6 +57,8 @@ go_test( "log_test.go", "styles_test.go", ], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/log/log.go b/core/log/log.go index e9be831921..b9d84e953f 100644 --- a/core/log/log.go +++ b/core/log/log.go @@ -150,7 +150,7 @@ func (l *Logger) Message(s Severity, stopProcess bool, text string) *Message { m := &Message{ Text: text, - Time: t.In(time.UTC), + Time: t.In(time.Local), Severity: s, StopProcess: stopProcess, Tag: l.tag, diff --git a/core/log/log_pb/BUILD.bazel b/core/log/log_pb/BUILD.bazel index f605410bd4..c8bead4069 100644 --- a/core/log/log_pb/BUILD.bazel +++ b/core/log/log_pb/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", diff --git a/core/log/testing.go b/core/log/testing.go index 4957dda186..0ee47ccc2f 100644 --- a/core/log/testing.go +++ b/core/log/testing.go @@ -24,14 +24,14 @@ func Testing(t delegate) context.Context { // SubTest returns the context with the TestHandler replaced with t. // This is intended to be used for sub-tests. For example: // -// func TestExample(t *testing.T) { -// ctx := log.Testing(t) -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// test.run(log.SubTest(ctx, t)) -// } -// } -// } +// func TestExample(t *testing.T) { +// ctx := log.Testing(t) +// for _, test := range tests { +// t.Run(test.name, func(t *testing.T) { +// test.run(log.SubTest(ctx, t)) +// } +// } +// } func SubTest(ctx context.Context, t delegate) context.Context { return PutHandler(ctx, TestHandler(t, Normal)) } diff --git a/core/math/f16/BUILD.bazel b/core/math/f16/BUILD.bazel index b23c861dda..95b8c7846d 100644 --- a/core/math/f16/BUILD.bazel +++ b/core/math/f16/BUILD.bazel @@ -28,5 +28,5 @@ go_test( name = "go_default_test", size = "small", srcs = ["float16_test.go"], - embed = [":go_default_library"], + deps = [":go_default_library"], ) diff --git a/core/math/f16/float16.go b/core/math/f16/float16.go index 803b0081ad..e4ffca3a09 100644 --- a/core/math/f16/float16.go +++ b/core/math/f16/float16.go @@ -19,11 +19,11 @@ import "unsafe" // Number represents a 16-bit floating point number, containing a single sign bit, 5 exponent bits // and 10 fractional bits. This corresponds to IEEE 754-2008 binary16 (or half precision float) type. // -// MSB LSB -// ╔════╦════╤════╤════╤════╤════╦════╤════╤════╤════╤════╤════╤════╤════╤════╤════╗ -// ║Sign║ E₄ │ E₃ │ E₂ │ E₁ │ E₀ ║ F₉ │ F₈ │ F₇ │ F₆ │ F₅ │ F₄ │ F₃ │ F₂ │ F₁ │ F₀ ║ -// ╚════╩════╧════╧════╧════╧════╩════╧════╧════╧════╧════╧════╧════╧════╧════╧════╝ -// Where E is the exponent bits and F is the fractional bits. +// MSB LSB +// ╔════╦════╤════╤════╤════╤════╦════╤════╤════╤════╤════╤════╤════╤════╤════╤════╗ +// ║Sign║ E₄ │ E₃ │ E₂ │ E₁ │ E₀ ║ F₉ │ F₈ │ F₇ │ F₆ │ F₅ │ F₄ │ F₃ │ F₂ │ F₁ │ F₀ ║ +// ╚════╩════╧════╧════╧════╧════╩════╧════╧════╧════╧════╧════╧════╧════╧════╧════╝ +// Where E is the exponent bits and F is the fractional bits. type Number uint16 const ( diff --git a/core/math/f32/BUILD.bazel b/core/math/f32/BUILD.bazel index 069014a2c6..99f6a12bfa 100644 --- a/core/math/f32/BUILD.bazel +++ b/core/math/f32/BUILD.bazel @@ -33,6 +33,8 @@ go_test( "vec3_test.go", "vec4_test.go", ], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/math/f32/f32.go b/core/math/f32/f32.go index e8439d4f2e..0d83718dfb 100644 --- a/core/math/f32/f32.go +++ b/core/math/f32/f32.go @@ -40,11 +40,12 @@ func MaxOf(a float32, b ...float32) float32 { // Round rounds v to the nearest integer. // Examples: -// Round(-0.9) = -1 -// Round(-0.1) = 0 -// Round(0.0) = 0 -// Round(0.1) = 0 -// Round(0.9) = 1 +// +// Round(-0.9) = -1 +// Round(-0.1) = 0 +// Round(0.0) = 0 +// Round(0.1) = 0 +// Round(0.9) = 1 func Round(v float32) int { if v < 0 { return int(math.Ceil(float64(v) - 0.5)) diff --git a/core/math/f64/BUILD.bazel b/core/math/f64/BUILD.bazel index afdb699ef8..7dcc95cfee 100644 --- a/core/math/f64/BUILD.bazel +++ b/core/math/f64/BUILD.bazel @@ -34,6 +34,8 @@ go_test( "vec3_test.go", "vec4_test.go", ], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + ], ) diff --git a/core/math/f64/f64.go b/core/math/f64/f64.go index 0ad9592c7e..e233069742 100644 --- a/core/math/f64/f64.go +++ b/core/math/f64/f64.go @@ -40,11 +40,12 @@ func MaxOf(a float64, b ...float64) float64 { // Round rounds v to the nearest integer. // Examples: -// Round(-0.9) = -1 -// Round(-0.1) = 0 -// Round(0.0) = 0 -// Round(0.1) = 0 -// Round(0.9) = 1 +// +// Round(-0.9) = -1 +// Round(-0.1) = 0 +// Round(0.0) = 0 +// Round(0.1) = 0 +// Round(0.9) = 1 func Round(v float64) int { if v < 0 { return int(math.Ceil(v - 0.5)) diff --git a/core/math/interval/list.go b/core/math/interval/list.go index 385a97ad77..6f9f9025c5 100644 --- a/core/math/interval/list.go +++ b/core/math/interval/list.go @@ -56,14 +56,16 @@ func IndexOf(l List, value uint64) int { // For example, consider the merging of intervals [0, 2] and [3, 5]: // // When joinAdj == false: -// ╭ ╮ ╭ ╮ ╭ ╮╭ ╮ -// │0 1 2│ merge │3 4 5│ = │0 1 2││3 4 5│ -// ╰ ╯ ╰ ╯ ╰ ╯╰ ╯ +// +// ╭ ╮ ╭ ╮ ╭ ╮╭ ╮ +// │0 1 2│ merge │3 4 5│ = │0 1 2││3 4 5│ +// ╰ ╯ ╰ ╯ ╰ ╯╰ ╯ // // When join == true: -// ╭ ╮ ╭ ╮ ╭ ╮ -// │0 1 2│ merge │3 4 5│ = │0 1 2 3 4 5│ -// ╰ ╯ ╰ ╯ ╰ ╯ +// +// ╭ ╮ ╭ ╮ ╭ ╮ +// │0 1 2│ merge │3 4 5│ = │0 1 2 3 4 5│ +// ╰ ╯ ╰ ╯ ╰ ╯ func Merge(l MutableList, span U64Span, joinAdj bool) int { return merge(l, span, joinAdj) } diff --git a/core/math/interval/value_list.go b/core/math/interval/value_list.go index 41f91797b4..a28b7a14d1 100644 --- a/core/math/interval/value_list.go +++ b/core/math/interval/value_list.go @@ -102,10 +102,10 @@ func (l *ValueSpanList) Delete(index int, count int) { } // Update modifies the values in `span` by applying the function `f`. -// - Parts of `span` that are outside the intervals in `l` are inserted with -// value `f(nil)`. -// - If `f` returns `nil`, the corresponding span is removed. -// - Adjacent intervals with the same value are merged. +// - Parts of `span` that are outside the intervals in `l` are inserted with +// value `f(nil)`. +// - If `f` returns `nil`, the corresponding span is removed. +// - Adjacent intervals with the same value are merged. func Update(l ValueList, span U64Span, f func(interface{}) interface{}) { k := Search(l, func(test U64Span) bool { return span.Start < test.End diff --git a/core/math/sint/BUILD.bazel b/core/math/sint/BUILD.bazel index f84f157733..c43d78635e 100644 --- a/core/math/sint/BUILD.bazel +++ b/core/math/sint/BUILD.bazel @@ -28,5 +28,5 @@ go_library( go_test( name = "go_default_test", srcs = ["sint_test.go"], - embed = [":go_default_library"], + deps = [":go_default_library"], ) diff --git a/core/math/sint/sint_test.go b/core/math/sint/sint_test.go index a69ab877a4..bfdc473536 100644 --- a/core/math/sint/sint_test.go +++ b/core/math/sint/sint_test.go @@ -4,7 +4,7 @@ // 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 +// 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, diff --git a/core/memory/arena/BUILD.bazel b/core/memory/arena/BUILD.bazel deleted file mode 100644 index 170c7bbf86..0000000000 --- a/core/memory/arena/BUILD.bazel +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright (C) 2018 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") - -go_library( - name = "go_default_library", - srcs = ["arena.go"], - cdeps = ["//core/memory/arena/cc"], - cgo = True, - clinkopts = [], # keep - importpath = "github.com/google/gapid/core/memory/arena", - visibility = ["//visibility:public"], - deps = [ - "//core/context/keys:go_default_library", - "//core/data/compare:go_default_library", - ], -) - -go_test( - name = "go_default_test", - size = "small", - srcs = ["arena_test.go"], - embed = [":go_default_library"], - deps = [ - "//core/assert:go_default_library", - "//core/log:go_default_library", - ], -) diff --git a/core/memory/arena/arena.go b/core/memory/arena/arena.go deleted file mode 100644 index a9ebd3c1b3..0000000000 --- a/core/memory/arena/arena.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// Package arena implements a memory arena. -package arena - -import ( - "context" - "fmt" - "unsafe" - - "github.com/google/gapid/core/data/compare" - - "github.com/google/gapid/core/context/keys" -) - -// #include "core/memory/arena/cc/arena.h" -import "C" - -// Arena is a native memory allocator that owns each of the allocations made by -// Allocate() and Reallocate(). If there are any outstanding allocations when -// the Arena is disposed then these allocations are automatically freed. -// Because the memory is allocated outside of the Go environment it is important -// to explicity free unused memory - either by calling Free() or calling -// Dispose() on the Arena. -// Failing to Dispose() the arena will leak memory. -type Arena struct{ Pointer unsafe.Pointer } - -// New constructs a new arena. -// You must call Dispose to free the arena object and any arena-owned -// allocations. -func New() Arena { - return Arena{Pointer: unsafe.Pointer(C.arena_create())} -} - -// Dispose destructs and frees the arena and all arena-owned allocations. -func (a Arena) Dispose() { - a.assertNotNil() - C.arena_destroy((*C.arena)(a.Pointer)) -} - -// Allocate returns a pointer to a new arena-owned, contiguous block of memory -// of the specified size and alignment. -func (a Arena) Allocate(size, alignment int) unsafe.Pointer { - a.assertNotNil() - return C.arena_alloc((*C.arena)(a.Pointer), C.uint32_t(size), C.uint32_t(alignment)) -} - -// Reallocate reallocates the memory at ptr to the new size and alignment. -// ptr must have been allocated from this arena or be nil. -func (a Arena) Reallocate(ptr unsafe.Pointer, size, alignment int) unsafe.Pointer { - a.assertNotNil() - return C.arena_realloc((*C.arena)(a.Pointer), ptr, C.uint32_t(size), C.uint32_t(alignment)) -} - -// Free releases the memory at ptr, which must have been previously allocated by -// this arena. -func (a Arena) Free(ptr unsafe.Pointer) { - a.assertNotNil() - C.arena_free((*C.arena)(a.Pointer), ptr) -} - -// Stats holds statistics of an Arena. -type Stats struct { - NumAllocations int - NumBytesAllocated int -} - -func (s Stats) String() string { - return fmt.Sprintf("{allocs: %v, bytes: %v}", s.NumAllocations, s.NumBytesAllocated) -} - -// Stats returns statistics of the current state of the Arena. -func (a Arena) Stats() Stats { - var numAllocs, numBytes C.size_t - a.assertNotNil() - C.arena_stats((*C.arena)(a.Pointer), &numAllocs, &numBytes) - return Stats{int(numAllocs), int(numBytes)} -} - -func (a Arena) assertNotNil() { - if a.Pointer == nil { - panic("nil arena") - } -} - -type arenaKeyTy string - -const arenaKey = arenaKeyTy("arena") - -// Get returns the Arena attached to the given context. -func Get(ctx context.Context) Arena { - if val := ctx.Value(arenaKey); val != nil { - return val.(Arena) - } - panic("arena missing from context") -} - -// Put amends a Context by attaching a Arena reference to it. -func Put(ctx context.Context, d Arena) context.Context { - if val := ctx.Value(arenaKey); val != nil { - panic("Context already holds an arena") - } - return keys.WithValue(ctx, arenaKey, d) -} - -// Offsetable is used as an anonymous field of types that require a current -// offset value. -type Offsetable struct{ Offset int } - -// AlignUp rounds-up the current offset so that is is a multiple of n. -func (o *Offsetable) AlignUp(n int) { - pad := n - o.Offset%n - if pad == n { - return - } - o.Offset += pad -} - -// Writer provides methods to help allocate and populate a native buffer with -// data. Use Arena.Writer() to construct. -type Writer struct { - Offsetable // The current write-offset in bytes. - arena Arena - size int - alignment int - base unsafe.Pointer - frozen bool -} - -// NewWriter returns a new Writer to a new arena allocated buffer of the initial -// size. The native buffer may grow if the writer exceeds the size of the -// buffer. The buffer will always be of the specified alignment in memory. -// The once the native buffer is no longer needed, the pointer returned by -// Pointer() should be passed to Arena.Free(). -func (a Arena) NewWriter(size, alignment int) *Writer { - base := a.Allocate(size, alignment) - return &Writer{ - arena: a, - size: size, - alignment: alignment, - base: base, - } -} - -// Reset sets the write offset back to the start of the buffer and unfreezes -// the writer. This allows for efficient reuse of the writer's native buffer. -func (w *Writer) Reset() { - w.Offset = 0 - w.frozen = false -} - -// Pointer returns the base address of the native buffer for the writer. -// Calling Pointer() freezes the writer - once called no more writes to the -// buffer can be made, unless Reset() is called. Freezing attempts to reduce the -// chance of the stale pointer being used after a buffer reallocation. -func (w *Writer) Pointer() unsafe.Pointer { - w.frozen = true - return w.base -} - -// Write copies size bytes from src to the current writer's write offset. -// If there is not enough space in the buffer for the write, then the buffer -// is grown via reallocation. -// Upon returning, the write offset is incremented by size bytes. -func (w *Writer) Write(src unsafe.Pointer, size int) { - if w.frozen { - panic("Cannot write to Writer after calling Pointer()") - } - if needed := w.Offset + size; needed > w.size { - size := w.size - for needed > size { - size *= 2 // TODO: Snugger fit? - } - w.base = w.arena.Reallocate(w.base, size, w.alignment) - w.size = size - } - dst := uintptr(w.base) + uintptr(w.Offset) - for i := 0; i < size; i++ { - dst := (*byte)(unsafe.Pointer(dst + uintptr(i))) - src := (*byte)(unsafe.Pointer(uintptr(src) + uintptr(i))) - *dst = *src - } - w.Offset += size -} - -// Reader provides the Read method to read native buffer data. -// Use NewReader() to construct. -type Reader struct { - Offsetable // The current read-offset in bytes. - base unsafe.Pointer -} - -// NewReader returns a new Reader to the native-buffer starting at ptr. -func NewReader(ptr unsafe.Pointer) *Reader { - return &Reader{base: ptr} -} - -// Read copies size bytes from the current read offset to dst. -// Upon returning, the read offset is incremented by size bytes. -func (r *Reader) Read(dst unsafe.Pointer, size int) { - src := uintptr(r.base) + uintptr(r.Offset) - for i := 0; i < size; i++ { - src := (*byte)(unsafe.Pointer(src + uintptr(i))) - dst := (*byte)(unsafe.Pointer(uintptr(dst) + uintptr(i))) - *dst = *src - } - r.Offset += size -} - -func init() { - // Don't compare arenas. - compare.Register(func(c compare.Comparator, a, b Arena) { - }) -} diff --git a/core/memory/arena/arena_test.go b/core/memory/arena/arena_test.go deleted file mode 100644 index 5f89ff80d8..0000000000 --- a/core/memory/arena/arena_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (C) 2018 Google Inc. -// -// 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. - -package arena_test - -import ( - "testing" - - "github.com/google/gapid/core/assert" - "github.com/google/gapid/core/log" - "github.com/google/gapid/core/memory/arena" -) - -func TestArenaStats(t *testing.T) { - ctx := log.Testing(t) - - a := arena.New() - defer a.Dispose() - - assert.For(ctx, "empty arena").That(a.Stats()).Equals(arena.Stats{}) - - a.Allocate(10, 4) - assert.For(ctx, "num alloc").ThatInteger(a.Stats().NumAllocations).Equals(1) - assert.For(ctx, "bytes alloc").ThatInteger(a.Stats().NumBytesAllocated).IsAtLeast(10) -} diff --git a/core/memory/arena/cc/arena.cpp b/core/memory/arena/cc/arena.cpp index cd5c9e23ea..800863d5d0 100644 --- a/core/memory/arena/cc/arena.cpp +++ b/core/memory/arena/cc/arena.cpp @@ -28,7 +28,7 @@ #include #if (TARGET_OS == GAPID_OS_LINUX) || (TARGET_OS == GAPID_OS_ANDROID) || \ - (TARGET_OS == GAPID_OS_OSX) + (TARGET_OS == GAPID_OS_OSX) || (TARGET_OS == GAPID_OS_FUCHSIA) #include #include // getpagesize #elif TARGET_OS == GAPID_OS_WINDOWS @@ -352,31 +352,3 @@ void Arena::unprotect() { } } // namespace core - -extern "C" { - -arena* arena_create() { return reinterpret_cast(new core::Arena()); } - -void arena_destroy(arena* a) { delete reinterpret_cast(a); } - -void* arena_alloc(arena* a, uint32_t size, uint32_t align) { - return reinterpret_cast(a)->allocate(size, align); -} - -void* arena_realloc(arena* a, void* ptr, uint32_t size, uint32_t align) { - return reinterpret_cast(a)->reallocate(ptr, size, align); -} - -void arena_free(arena* a, void* ptr) { - reinterpret_cast(a)->free(ptr); -} - -// arena_stats returns statistics of the current state of the arena. -void arena_stats(arena* a, size_t* num_allocations, - size_t* num_bytes_allocated) { - auto arena = reinterpret_cast(a); - *num_allocations = arena->num_allocations(); - *num_bytes_allocated = arena->num_bytes_allocated(); -} - -} // extern "C" diff --git a/core/memory/arena/cc/arena.h b/core/memory/arena/cc/arena.h index 5332f3097a..ee0c23d668 100644 --- a/core/memory/arena/cc/arena.h +++ b/core/memory/arena/cc/arena.h @@ -18,8 +18,6 @@ #include #include -#ifdef __cplusplus - #include #include #include @@ -162,37 +160,4 @@ inline void Arena::destroy(T* ptr) { } // namespace core -extern "C" { -#endif - -// C handle for an arena. -typedef struct arena_t arena; - -// arena_create constructs and returns a new arena. -arena* arena_create(); - -// arena_destroy destructs the specified arena, freeing all allocations -// made by that arena. Once destroyed, you must not use the arena. -void arena_destroy(arena* arena); - -// arena_alloc creates a memory allocation in the specified arena of the -// given size and alignment. -void* arena_alloc(arena* arena, uint32_t size, uint32_t align); - -// arena_realloc reallocates the memory at ptr to the new size and -// alignment. ptr must have been allocated from arena. -void* arena_realloc(arena* arena, void* ptr, uint32_t size, uint32_t align); - -// arena_free deallocates the memory at ptr. ptr must have been allocated -// from arena. -void arena_free(arena* arena, void* ptr); - -// arena_stats returns statistics of the current state of the arena. -void arena_stats(arena* arena, size_t* num_allocations, - size_t* num_bytes_allocated); - -#ifdef __cplusplus -} // extern "C" -#endif - #endif // CORE_ARENA_H diff --git a/core/memory/arena/cc/arena_test.cpp b/core/memory/arena/cc/arena_test.cpp index 6f866e2e7c..c03e551b80 100644 --- a/core/memory/arena/cc/arena_test.cpp +++ b/core/memory/arena/cc/arena_test.cpp @@ -117,8 +117,8 @@ TEST_P(reallocate_memory_tests, reallocate) { EXPECT_EQ(got, pattern); } -INSTANTIATE_TEST_CASE_P(many_values, reallocate_memory_tests, - ::testing::Values(1, 15, 16, 31, 32, 44, 1024, 4093)); +INSTANTIATE_TEST_SUITE_P(many_values, reallocate_memory_tests, + ::testing::Values(1, 15, 16, 31, 32, 44, 1024, 4093)); using memory_protection_tests = ::testing::TestWithParam; @@ -149,8 +149,8 @@ TEST_P(memory_protection_tests, unprotect_memory) { // DO NOT add another death test. // There is some wonderful weirdness about how this will handle // the offsets. -INSTANTIATE_TEST_CASE_P(many_values, memory_protection_tests, - ::testing::Values(1, 31, 32, 44, 1024, 4093)); +INSTANTIATE_TEST_SUITE_P(many_values, memory_protection_tests, + ::testing::Values(1, 31, 32, 44, 1024, 4093)); } // namespace test } // namespace core \ No newline at end of file diff --git a/core/memory_tracker/cc/memory_protections.h b/core/memory_tracker/cc/memory_protections.h index 8780591375..1052c19858 100644 --- a/core/memory_tracker/cc/memory_protections.h +++ b/core/memory_tracker/cc/memory_protections.h @@ -30,4 +30,4 @@ enum class PageProtections { } // namespace track_memory } // namespace gapii -#endif // GAPII_MEMORY_PROTECTIONS_H \ No newline at end of file +#endif // GAPII_MEMORY_PROTECTIONS_H diff --git a/core/memory_tracker/cc/memory_tracker.h b/core/memory_tracker/cc/memory_tracker.h index af914ee0b4..dbb5613afa 100644 --- a/core/memory_tracker/cc/memory_tracker.h +++ b/core/memory_tracker/cc/memory_tracker.h @@ -471,7 +471,7 @@ class MemoryTrackerImpl : public SpecificMemoryTracker { return result; } - // Dummy function that we can pass down to the specific memory tracker. + // Placeholder function that we can pass down to the specific memory tracker. bool DoHandleSegfault(void* v) { return HandleSegfault(v); } // A helper function that returns the first tracking range that overlaps with diff --git a/core/memory_tracker/cc/memory_tracker_test.cpp b/core/memory_tracker/cc/memory_tracker_test.cpp index f622984c00..c570b5243d 100644 --- a/core/memory_tracker/cc/memory_tracker_test.cpp +++ b/core/memory_tracker/cc/memory_tracker_test.cpp @@ -129,7 +129,7 @@ class TestFixture : public ::testing::Test { mutex_thread_init_order_.lock(); normal_thread_run_before_signal_handler_.lock(); m_.lock(); - dummy_lock_.store(false, std::memory_order_seq_cst); + fake_lock_.store(false, std::memory_order_seq_cst); deadlocked_.store(false, std::memory_order_seq_cst); unique_test_ = this; } @@ -150,29 +150,29 @@ class TestFixture : public ::testing::Test { thread_second.join(); } - // DoTask acquires the dummy lock and runs the given function. + // DoTask acquires the fake lock and runs the given function. void DoTask(std::function task) { - DummyLock(); + FakeLock(); normal_thread_run_before_signal_handler_ .unlock(); // This is to make sure the signal will only be send after // the normal threads task(); - DummyUnlock(); + FakeUnlock(); } // RegisterHandlerAndTriggerSignal does the following things: - // 1) Registers a signal handler to SIGUSR1 which acquires the dummy lock. - // 2) Creates a child thread which will acquire the dummy lock. + // 1) Registers a signal handler to SIGUSR1 which acquires the fake lock. + // 2) Creates a child thread which will acquire the fake lock. // 3) Waits for the child thread to run then sends a signal interrupt to the // child thread. // 4) Waits until the child thread finishes and clean up. void RegisterHandlerAndTriggerSignal(void* (*child_thread_func)(void*)) { auto handler_func = [](int, siginfo_t*, void*) { - // DummyLock is to simulate the case that the coherent memory tracker's + // FakeLock is to simulate the case that the coherent memory tracker's // signal handler needs to access shared data. - unique_test_->DummyLock(); + unique_test_->FakeLock(); ; - unique_test_->DummyUnlock(); + unique_test_->FakeUnlock(); }; struct sigaction orig_action; ASSERT_TRUE(RegisterSignalHandler(SIGUSR1, handler_func, &orig_action)); @@ -186,16 +186,16 @@ class TestFixture : public ::testing::Test { } protected: - // Acquires the dummy lock. If the dummy lock has already been locked, i.e. + // Acquires the fake lock. If the fake lock has already been locked, i.e. // its value is true, sets the deadlocked_ flag. - void DummyLock() { - if (dummy_lock_.load(std::memory_order_seq_cst)) { + void FakeLock() { + if (fake_lock_.load(std::memory_order_seq_cst)) { deadlocked_.exchange(true); } - dummy_lock_.exchange(true); + fake_lock_.exchange(true); } - // Releases the dummy lock by setting its value to false. - void DummyUnlock() { dummy_lock_.exchange(false); } + // Releases the fake lock by setting its value to false. + void FakeUnlock() { fake_lock_.exchange(false); } std::mutex normal_thread_run_before_signal_handler_; // A mutex to // guarantee the normal @@ -206,7 +206,7 @@ class TestFixture : public ::testing::Test { // initiated before the other one std::mutex m_; // A mutex for tuning execution order in tests std::atomic - dummy_lock_; // An atomic bool to simulate the lock/unlock state + fake_lock_; // An atomic bool to simulate the lock/unlock state std::atomic deadlocked_; // A flag to indicate deadlocked or not state static TestFixture* unique_test_; // A static pointer of this test fixture, @@ -225,28 +225,6 @@ void* VoidPointerAdd(void* addr, ssize_t offset) { using SpinLockTest = TestFixture; -// Thread A sleeps first, then increases the counter, while Thread B will -// increase the counter before Thread A wakes up. -TEST_F(SpinLockTest, WithoutSpinLockGuard) { - Init(); - uint32_t counter = 0u; - RunInTwoThreads( - // Thread A is initiated first and runs first. - [this, &counter]() { - m_.unlock(); - usleep(5000); - counter++; - EXPECT_EQ(2u, counter); - }, - // Thread B is initiated secondly and runs after thread A runs. - [this, &counter]() { - m_.lock(); - counter++; - EXPECT_EQ(1u, counter); - }); - EXPECT_EQ(2u, counter); -} - // Thread A sleeps first, then increases the counter, but Thread B waits for // the spin lock. So Thread A increases the counter before Thread B. TEST_F(SpinLockTest, WithSpinLockGuard) { @@ -275,24 +253,6 @@ TEST_F(SpinLockTest, WithSpinLockGuard) { using SignalBlockerTest = TestFixture; -// A thread acquires the a lock first, then a signal interrupts the thread and -// acquires the lock again. This results into a deadlock. -TEST_F(SignalBlockerTest, WithoutBlocker) { - Init(); - auto normal_thread_func = [](void*) -> void* { - unique_test_->DoTask([]() { usleep(5000); }); - return nullptr; - }; - - std::thread start_normal_thread_and_send_signal( - [this, &normal_thread_func]() { - RegisterHandlerAndTriggerSignal(normal_thread_func); - }); - start_normal_thread_and_send_signal.join(); - // Expect dead locked - EXPECT_TRUE(deadlocked_.load(std::memory_order_seq_cst)); -} - // A thread acquires the a lock first, then a signal tries to interrupt the // thread. But the signal is blocked, signal handler is called after the thread // finishes its job. This avoids dead lock. @@ -353,31 +313,6 @@ TEST_F(SignalBlockerTest, BlockerRecursiveSafe) { using WrapperTest = TestFixture; -// Call the member function: DoTask() without spin lock guard. -TEST_F(WrapperTest, WithoutSpinLockGuardedWrapper) { - Init(); - uint32_t counter = 0u; - RunInTwoThreads( - // Initiated first - [this, &counter]() { - DoTask([this, &counter]() { - m_.unlock(); - usleep(5000); - counter++; - EXPECT_EQ(2u, counter); - }); - }, - // Initiated secondly - [this, &counter]() { - DoTask([this, &counter]() { - m_.lock(); - counter++; - EXPECT_EQ(1u, counter); - }); - }); - EXPECT_EQ(2u, counter); -} - // Call the member function: DoTask() with spin lock guarded. TEST_F(WrapperTest, WithSpinLockGuardedWrapper) { Init(); @@ -401,7 +336,7 @@ TEST_F(WrapperTest, WithSpinLockGuardedWrapper) { // Initiated secondly [this, &counter, &LockedDoTask]() { m_.lock(); - LockedDoTask([this, &counter]() { + LockedDoTask([&counter]() { counter++; EXPECT_EQ(2u, counter); }); @@ -409,22 +344,6 @@ TEST_F(WrapperTest, WithSpinLockGuardedWrapper) { EXPECT_EQ(2u, counter); } -// Without a signal safe wrapper, spin lock acquired in function may cause -// deadlock. -TEST_F(WrapperTest, WithoutSignalSafeWrapper) { - Init(); - auto normal_thread_func = [](void*) -> void* { - unique_test_->DoTask([]() { usleep(5000); }); - return nullptr; - }; - std::thread start_normal_thread_and_send_signal( - [this, &normal_thread_func]() { - RegisterHandlerAndTriggerSignal(normal_thread_func); - }); - start_normal_thread_and_send_signal.join(); - EXPECT_TRUE(deadlocked_.load(std::memory_order_seq_cst)); -} - // With a signal safe wrapper, signal interrupt will be blocked, so signal // handler will not cause a deadlock. TEST_F(WrapperTest, WithSignalSafeWrapper) { @@ -458,7 +377,7 @@ class MarkListTest : public ::testing::Test { using MarkListTestTypes = ::testing::Types; -TYPED_TEST_CASE(MarkListTest, MarkListTestTypes); +TYPED_TEST_SUITE(MarkListTest, MarkListTestTypes); TYPED_TEST(MarkListTest, NoSpace) { auto m = this->CreateMarkList(0); @@ -858,7 +777,7 @@ namespace { template class SilentSignal { public: - SilentSignal() : succeeded_(false), old_sigaction_{0} { + SilentSignal() : succeeded_(false), old_sigaction_{} { succeeded_ = RegisterSignalHandler(SIG, IgnoreSignal, &old_sigaction_); } ~SilentSignal() { @@ -1063,14 +982,13 @@ TEST(MemoryTrackerTest, ManyPagesMultithread) { threads.reserve(num_threads); // Every thread is responsible for 4 continuous pages. for (uint32_t ti = 0; ti < num_threads; ti++) { - threads.emplace_back(std::thread( - [mem_start_addr, num_pages_per_thread, ti, page_size, &t]() { - size_t thread_range_size = num_pages_per_thread * page_size; - void* thread_range_start = - VoidPointerAdd(mem_start_addr, ti * thread_range_size); - EXPECT_TRUE(t.TrackRange(thread_range_start, thread_range_size)); - memset(thread_range_start, 0xFF, thread_range_size); - })); + threads.emplace_back(std::thread([mem_start_addr, ti, page_size, &t]() { + size_t thread_range_size = num_pages_per_thread * page_size; + void* thread_range_start = + VoidPointerAdd(mem_start_addr, ti * thread_range_size); + EXPECT_TRUE(t.TrackRange(thread_range_start, thread_range_size)); + memset(thread_range_start, 0xFF, thread_range_size); + })); } std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); }); @@ -1078,8 +996,7 @@ TEST(MemoryTrackerTest, ManyPagesMultithread) { // All the pages should have been recorded. std::vector dirty_pages; t.HandleAndClearDirtyIntersects( - m.mem(), num_pages * page_size, - [&dirty_pages, page_size](void* addr, size_t size) { + m.mem(), num_pages * page_size, [&dirty_pages](void* addr, size_t size) { uintptr_t casted_addr = reinterpret_cast(addr); for (size_t i = 0; i < size / GetPageSize(); i++) { dirty_pages.push_back( diff --git a/core/memory_tracker/cc/posix/memory_tracker.inc b/core/memory_tracker/cc/posix/memory_tracker.inc index 82e0d8bb73..6f72445ac1 100644 --- a/core/memory_tracker/cc/posix/memory_tracker.inc +++ b/core/memory_tracker/cc/posix/memory_tracker.inc @@ -22,10 +22,10 @@ namespace track_memory { class PosixMemoryTracker { public: PosixMemoryTracker(std::function segfault_function) - : orig_action_{0}, handle_segfault_(segfault_function) {} + : orig_action_(), handle_segfault_(segfault_function) {} bool IsInstalled() const { - struct sigaction orig_action = {0}; + struct sigaction orig_action = {}; sigaction(SIGSEGV, nullptr, &orig_action); return orig_action.sa_sigaction == &SegfaultHandlerFunction; } @@ -77,9 +77,7 @@ bool inline PosixMemoryTracker::EnableMemoryTrackerImpl() { } unique_tracker = this; - struct sigaction sa { - 0 - }; + struct sigaction sa = {}; sa.sa_flags = SA_SIGINFO; sigemptyset(&sa.sa_mask); sa.sa_sigaction = &SegfaultHandlerFunction; diff --git a/core/net/grpcutil/BUILD.bazel b/core/net/grpcutil/BUILD.bazel index e1092f9864..1fb20ef5dd 100644 --- a/core/net/grpcutil/BUILD.bazel +++ b/core/net/grpcutil/BUILD.bazel @@ -39,8 +39,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["pipe_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/event/task:go_default_library", "//core/log:go_default_library", diff --git a/core/net/useragent.go b/core/net/useragent.go index 0e131fcbe3..da7e326cd0 100644 --- a/core/net/useragent.go +++ b/core/net/useragent.go @@ -46,8 +46,6 @@ func UserAgent(d *device.Configuration, ai ApplicationInfo) string { case device.Linux: info = append(info, "Linux") - case device.Stadia: - info = append(info, "Stadia") case device.Android: info = append(info, "Linux", "U", fmt.Sprintf("Android %v.%v.%v", os.MajorVersion, os.MinorVersion, os.PointVersion)) diff --git a/core/os/android/BUILD.bazel b/core/os/android/BUILD.bazel index f39a641e41..038f08e07e 100644 --- a/core/os/android/BUILD.bazel +++ b/core/os/android/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", diff --git a/core/os/android/adb/BUILD.bazel b/core/os/android/adb/BUILD.bazel index ccbcb25b08..fe930bdcf4 100644 --- a/core/os/android/adb/BUILD.bazel +++ b/core/os/android/adb/BUILD.bazel @@ -65,8 +65,8 @@ go_test( "logcat_test.go", "screen_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/event/task:go_default_library", "//core/log:go_default_library", diff --git a/core/os/android/adb/adb_data_test.go b/core/os/android/adb/adb_data_test.go index 464510a072..432c4d61eb 100644 --- a/core/os/android/adb/adb_data_test.go +++ b/core/os/android/adb/adb_data_test.go @@ -53,6 +53,7 @@ screen_off_locked_device offline screen_off_unlocked_device offline screen_on_locked_device offline screen_on_unlocked_device device +serial_do_match device `) emptyDevices = stub.RespondTo(adbPath.System()+` devices`, ` List of devices attached @@ -94,7 +95,10 @@ Activity Resolver Table: Packages: Package [com.google.foo] (ffffffc): userId=12345 - primaryCpuAbi=armeabi-v7a + *** extra whitespace on next line is for testing https://github.com/google/agi/issues/1077 *** + *** Do not remove it, doing so will make the tests fail *** + +primaryCpuAbi=armeabi-v7a secondaryCpuAbi=null versionCode=902107 minSdk=14 targetSdk=15 flags=[ HAS_CODE ALLOW_CLEAR_USER_DATA ALLOW_BACKUP ] @@ -140,6 +144,13 @@ mShowingDream=false mShowingLockscreen=false mDreamingSleepToken=null mStatusBar=Window{5033a83 u0 StatusBar} isStatusBarKeyguard=false ...`), + // The 'wm dismiss-keyguard' command is used to unlock the screen, it + // doesn't return anything + stub.RespondTo(adbPath.System()+` -s screen_off_locked_device shell wm dismiss-keyguard`, ""), + stub.RespondTo(adbPath.System()+` -s screen_off_unlocked_device shell wm dismiss-keyguard`, ""), + stub.RespondTo(adbPath.System()+` -s screen_on_locked_device shell wm dismiss-keyguard`, ""), + stub.RespondTo(adbPath.System()+` -s screen_on_unlocked_device shell wm dismiss-keyguard`, ""), + // Pid queries. stub.Regex(`adb -s ok_pgrep_\S*device shell pgrep .* com.google.foo`, stub.Respond("")), stub.Regex(`adb -s ok_pgrep\S*device shell pgrep -n -f com.google.bar`, stub.Respond("2778")), diff --git a/core/os/android/adb/bind.go b/core/os/android/adb/bind.go index 43835e4421..c334b80c2d 100644 --- a/core/os/android/adb/bind.go +++ b/core/os/android/adb/bind.go @@ -17,6 +17,7 @@ package adb import ( "context" + "github.com/google/gapid/core/app" "github.com/google/gapid/core/os/android" "github.com/google/gapid/core/os/device/bind" "github.com/google/gapid/core/os/shell" @@ -36,8 +37,17 @@ type Device interface { Forward(ctx context.Context, local, device Port) error // RemoveForward removes a port forward made by Forward. RemoveForward(ctx context.Context, local Port) error - // GraphicsDriver queries and returns info on the preview graphics driver. + // GraphicsDriver queries and returns info about the prerelease graphics driver. GraphicsDriver(ctx context.Context) (Driver, error) + // PrepareGpuProfiling queries GPU profiling support, and when profiling is supported it sets up + // the device for profiling of installedPackage app. It returns: + // - a bool which is true when GPU profiling is supported and the setup is done without errors + // - a string that contains the name of the package where to find profiling layers, this string + // is empty if there is no profiling layer required + // - a cleanup function to revert the device settings after profiling is done + // - an error to indicate if anything went wrong + // The returned bool disambiguates between "an error happened" and "profiling is not supported". + PrepareGpuProfiling(ctx context.Context, installedPackage *android.InstalledPackage) (bool, string, app.Cleanup, error) } // Driver contains the information about a graphics driver. @@ -67,3 +77,9 @@ type binding struct { // verify that binding implements Device var _ Device = (*binding)(nil) + +// InstallApp implements the bind.Device interface, which can be implemented using +// just the android.Device interface. +func (b *binding) InstallApp(ctx context.Context, path string) error { + return b.InstallAPK(ctx, path, true /*reinstall*/, true /*grant permissions*/) +} diff --git a/core/os/android/adb/commands.go b/core/os/android/adb/commands.go index a60c775ec8..5d9760c47f 100644 --- a/core/os/android/adb/commands.go +++ b/core/os/android/adb/commands.go @@ -15,7 +15,6 @@ package adb import ( - "bufio" "bytes" "context" "encoding/base64" @@ -30,7 +29,6 @@ import ( "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/android" "github.com/google/gapid/core/os/device" - "github.com/google/gapid/core/os/shell" "github.com/google/gapid/gapis/perfetto" common_pb "protos/perfetto/common" @@ -41,14 +39,20 @@ const ( // production build as is not 'rooted'. ErrDeviceNotRooted = fault.Const("Device is not a userdebug build") ErrRootFailed = fault.Const("Device failed to switch to root") + ErrUnrootFailed = fault.Const("Device failed to switch out of root") maxRootAttempts = 5 gpuRenderStagesDataSourceDescriptorName = "gpu.renderstages" + gpuMemTotalDataSourceDescriptorName = "android.gpu.memory" perfettoPort = NamedFileSystemSocket("/dev/socket/traced_consumer") - driverOverride = "gapid.driver_package_override" driverProperty = "ro.gfx.driver.1" + + gpuDebugProperty = "debug.graphics.gpu.profiler.perfetto" + + systemImageGpuProfilerSupportProperty = "graphics.gpu.profiler.support" + gpuProfilerVulkanLayerApkProperty = "graphics.gpu.profiler.vulkan_layer_apk" ) func isRootSuccessful(line string) bool { @@ -63,6 +67,17 @@ func isRootSuccessful(line string) bool { return false } +func isUnrootSuccessful(line string) bool { + for _, expected := range []string{ + "adbd not running as root", + } { + if line == expected { + return true + } + } + return false +} + // Root restarts adb as root. If the device is running a production build then // Root will return ErrDeviceNotRooted. func (b *binding) Root(ctx context.Context) error { @@ -99,6 +114,39 @@ retry: return log.Err(ctx, ErrRootFailed, buf.String()) } +// Unroot restarts adb as not-root. +func (b *binding) Unroot(ctx context.Context) error { + buf := bytes.Buffer{} + buf.WriteString("adb unroot gave output:") +retry: + for attempt := 0; attempt < maxRootAttempts; attempt++ { + output, err := b.Command("unroot").Call(ctx) + if err != nil { + return err + } + if len(output) == 0 { + return nil // Assume no output is success + } + output = strings.Replace(output, "\r\n", "\n", -1) // Not expected, but let's be safe. + buf.WriteString(fmt.Sprintf("\n#%d: %v", attempt, output)) + lines := strings.Split(output, "\n") + for i := len(lines) - 1; i >= 0; i-- { + line := lines[i] + if isUnrootSuccessful(line) { + return nil // Success + } + switch line { + case "restarting adbd as non root": + time.Sleep(time.Millisecond * 100) + continue retry + default: + // Some output we weren't expecting. + } + } + } + return log.Err(ctx, ErrUnrootFailed, buf.String()) +} + // IsDebuggableBuild returns true if the device runs a debuggable Android build. func (b *binding) IsDebuggableBuild(ctx context.Context) (bool, error) { output, err := b.Command("shell", "getprop", "ro.debuggable").Call(ctx) @@ -120,7 +168,19 @@ func (b *binding) InstallAPK(ctx context.Context, path string, reinstall bool, g // during installation. Before API 23, the flag did not exist. args = append(args, "-g") } + if b.Instance().GetConfiguration().GetOS().GetAPIVersion() >= 30 { + // Starting with API 30, non-system applications can not be queried by + // application targeting api level 30 by default. This flag allows the + // installed apk to be queryable. + args = append(args, "--force-queryable") + } args = append(args, path) + + // We unroot here for b/180757454. That is a problem installing APKs on some root phones. + // There is a theoretical race condition on the root/unroot status with packageinfo.go:PackageList() + // Because adb root/unroot is outside the program's state there's not much we can do to prevent this + // race conditon though. Someone could always run adb from a terminal at a bad time... + b.Unroot(ctx) return b.Command("install", args...).Run(ctx) } @@ -237,43 +297,12 @@ func (b *binding) DeleteSystemSetting(ctx context.Context, namespace, key string return nil } -// TempFile creates a temporary file on the given Device. It returns the -// path to the file, and a function that can be called to clean it up. -func (b *binding) TempFile(ctx context.Context) (string, func(ctx context.Context), error) { - res, err := b.Shell("mktemp").Call(ctx) - if err != nil { - return "", nil, err - } - return res, func(ctx context.Context) { - b.Shell("rm", "-f", res).Call(ctx) - }, nil -} - -// FileContents returns the contents of a given file on the Device. -func (b *binding) FileContents(ctx context.Context, path string) (string, error) { - return b.Shell("cat", path).Call(ctx) -} - // RemoveFile removes the given file from the device func (b *binding) RemoveFile(ctx context.Context, path string) error { _, err := b.Shell("rm", "-f", path).Call(ctx) return err } -// GetEnv returns the default environment for the Device. -func (b *binding) GetEnv(ctx context.Context) (*shell.Env, error) { - env, err := b.Shell("env").Call(ctx) - if err != nil { - return nil, err - } - scanner := bufio.NewScanner(strings.NewReader(env)) - e := &shell.Env{} - for scanner.Scan() { - e.Add(scanner.Text()) - } - return e, nil -} - func (b *binding) SupportsPerfetto(ctx context.Context) bool { os := b.Instance().GetConfiguration().GetOS() return os.GetAPIVersion() >= 28 @@ -318,6 +347,8 @@ func (b *binding) EnsurePerfettoPersistent(ctx context.Context) error { return nil } +// QueryPerfettoServiceState queries all existing perfetto data sources +// regardless of which it is registered from. func (b *binding) QueryPerfettoServiceState(ctx context.Context) (*device.PerfettoCapability, error) { result := b.To.Configuration.PerfettoCapability if result == nil { @@ -325,29 +356,52 @@ func (b *binding) QueryPerfettoServiceState(ctx context.Context) (*device.Perfet GpuProfiling: &device.GPUProfiling{}, } } - gpu := result.GpuProfiling - if gpu == nil { - gpu = &device.GPUProfiling{} - result.GpuProfiling = gpu + + if !b.SupportsPerfetto(ctx) { + return result, fmt.Errorf("Perfetto is not supported on this device") } - if b.Instance().GetConfiguration().GetOS().GetName() == "R" { - // TODO(b/146384733): Change this to API version when it releases - gpu.HasFrameLifecycle = true + gpu, err := b.QueryPerfettoGpuProfilingDataSources(ctx) + if err != nil { + return result, log.Errf(ctx, err, "Failed to query perfetto GPU profiling data sources.") } + result.GpuProfiling = gpu - if !b.SupportsPerfetto(ctx) { - return result, fmt.Errorf("Perfetto is not supported on this device") + if b.Instance().GetConfiguration().GetOS().GetAPIVersion() >= 30 { + // SurfaceFlinger frame lifecycle perfetto producer is mandated by Android 11 CTS, hence it will + // always exist. + result.HasFrameLifecycle = true + result.CanProvideTraceFilePath = true + + // This has anecdotally not worked well in Q, but appears to be fine in R. + result.CanDownloadWhileTracing = true + } + + services, err := b.Shell("service", "list").Call(ctx) + if err != nil { + return result, log.Errf(ctx, err, "Failed to query service list when trying to know power rail capability.") + } + if strings.Contains(services, "android.hardware.power.stats.IPowerStats/default") { + result.HasPowerRail = true } + return result, nil +} +// QueryPerfettoGpuProfilingDataSources queries and returns the data sources +// that support the GPU profiling functionailities, it includes: +// 1. gpu.counters +// 2. gpu.renderstages +// 3. android.gpu.memory +func (b *binding) QueryPerfettoGpuProfilingDataSources(ctx context.Context) (*device.GPUProfiling, error) { + gpu := &device.GPUProfiling{} encoded, err := b.Shell("perfetto", "--query-raw", "|", "base64").Call(ctx) if err != nil { - return result, log.Errf(ctx, err, "adb shell perfetto returned error: %s", encoded) + return gpu, log.Errf(ctx, err, "adb shell perfetto returned error: %s", encoded) } decoded, _ := base64.StdEncoding.DecodeString(encoded) state := &common_pb.TracingServiceState{} if err = proto.Unmarshal(decoded, state); err != nil { - return result, log.Errf(ctx, err, "Unmarshal returned error") + return gpu, log.Errf(ctx, err, "Unmarshal returned error") } for _, ds := range state.GetDataSources() { @@ -356,12 +410,16 @@ func (b *binding) QueryPerfettoServiceState(ctx context.Context) (*device.Perfet gpu.HasRenderStage = true continue } + if desc.GetName() == gpuMemTotalDataSourceDescriptorName { + gpu.HasGpuMemTotal = true + continue + } counters := desc.GetGpuCounterDescriptor().GetSpecs() if len(counters) != 0 { if gpu.GpuCounterDescriptor == nil { gpu.GpuCounterDescriptor = &device.GpuCounterDescriptor{} } - // We mirror the Perfetto GpuCounterDescriptor proto into GAPID, hence + // We mirror the Perfetto GpuCounterDescriptor proto into AGI, hence // they are binary format compatible. data, err := proto.Marshal(desc.GetGpuCounterDescriptor()) if err != nil { @@ -370,46 +428,90 @@ func (b *binding) QueryPerfettoServiceState(ctx context.Context) (*device.Perfet proto.UnmarshalMerge(data, gpu.GpuCounterDescriptor) } } - return result, nil + return gpu, nil } -func extrasFlags(extras []android.ActionExtra) []string { - flags := []string{} - for _, e := range extras { - flags = append(flags, e.Flags()...) +// Currently using Android Q/10, API 29 as ANGLE support cut-off +func (b *binding) SupportsAngle(ctx context.Context) bool { + os := b.Instance().GetConfiguration().GetOS() + return os.GetAPIVersion() >= 29 +} + +func (b *binding) QueryAngle(ctx context.Context) (*device.ANGLE, error) { + if !b.SupportsAngle(ctx) { + return nil, fmt.Errorf("ANGLE not supported on this device") + } + // ANGLE supported, so check for installed ANGLE package + // Favor custom installed packages first, followed by default system package + supported := []string{ + "org.chromium.angle.agi", + "org.chromium.angle", + "com.google.android.angle", + } + // Check installed packages for ANGLE package + packages, _ := b.InstalledPackages(ctx) + for _, angle := range supported { + if pkg := packages.FindByName(angle); pkg != nil { + return &device.ANGLE{ + Package: angle, + Version: int32(pkg.VersionCode), + }, nil + } } - return flags + return nil, fmt.Errorf("No ANGLE packages installed on this device") } -// DriverPackage queries and returns the package of the preview graphics driver. -func (b *binding) GraphicsDriver(ctx context.Context) (Driver, error) { - // Check if there is an override setup. - driver, err := b.SystemSetting(ctx, "global", driverOverride) +func SetupAngle(ctx context.Context, d Device, p *android.InstalledPackage) (app.Cleanup, error) { + // Restore ANGLE settings during app cleanup + angleDriverValues, err := d.SystemSetting(ctx, "global", "angle_gl_driver_selection_values") + if err != nil { + return nil, log.Err(ctx, err, "Failed to get original ANGLE driver selection name.") + } + angleDriverPkgs, err := d.SystemSetting(ctx, "global", "angle_gl_driver_selection_pkgs") if err != nil { - driver = "" + return nil, log.Err(ctx, err, "Failed to get original ANGLE enabled packages.") } + anglePackage, err := d.SystemSetting(ctx, "global", "angle_debug_package") + if err != nil { + return nil, log.Err(ctx, err, "Failed to get original ANGLE package name.") + } + log.I(ctx, "Saved original ANGLE pkg %s for app %s to restore during cleanup.", anglePackage, angleDriverPkgs) + // We successfully saved old settings, now set new ANGLE values for tracing + d.SetSystemSetting(ctx, "global", "angle_gl_driver_selection_values", "angle") + d.SetSystemSetting(ctx, "global", "angle_debug_package", d.Instance().GetConfiguration().GetAngle().GetPackage()) + d.SetSystemSetting(ctx, "global", "angle_gl_driver_selection_pkgs", p.Name) + // Enable ANGLE debug markers. + d.SetSystemProperty(ctx, "debug.angle.markers", "1") + // Return cleanup function to restore original ANGLE settings + return func(ctx context.Context) { + d.SetSystemSetting(ctx, "global", "angle_gl_driver_selection_values", angleDriverValues) + d.SetSystemSetting(ctx, "global", "angle_gl_driver_selection_pkgs", angleDriverPkgs) + d.SetSystemSetting(ctx, "global", "angle_debug_package", anglePackage) + d.SetSystemProperty(ctx, "debug.angle.markers", "") + }, nil +} - if driver == "" || driver == "null" { - driver, err = b.SystemProperty(ctx, driverProperty) - if err != nil { - return Driver{}, err - } - if driver == "" { - // There is no prerelease driver. - return Driver{}, nil - } +func extrasFlags(extras []android.ActionExtra) []string { + flags := []string{} + for _, e := range extras { + flags = append(flags, e.Flags()...) } + return flags +} - // Check the package path of the driver. +func (b *binding) resolveDriverPath(ctx context.Context, driver string) (Driver, error) { path, err := b.Shell("pm", "path", driver).Call(ctx) if err != nil { return Driver{}, err } + // Check the package path of the driver. if !strings.HasPrefix(path, "package:") { return Driver{}, nil } path = path[8:] - if path == "" { + // If the driver package path doesn't have the /data/app prefix, it means the returned + // path is the preinstalled emtpy prerelease driver. Hence don't return it. + if path == "" || !strings.HasPrefix(path, "/data/app") { return Driver{}, nil } @@ -418,3 +520,81 @@ func (b *binding) GraphicsDriver(ctx context.Context) (Driver, error) { Path: path, }, nil } + +// GraphicsDriver queries and returns info about the prerelease graphics driver. +func (b *binding) GraphicsDriver(ctx context.Context) (Driver, error) { + driver, err := b.SystemProperty(ctx, driverProperty) + if err != nil { + return Driver{}, err + } + if driver == "" { + // There is no prerelease driver. + return Driver{}, nil + } + return b.resolveDriverPath(ctx, driver) +} + +// Returns the package version code of the graphics driver +func (b *binding) DriverVersionCode(ctx context.Context) (int, error) { + driver, err := b.SystemProperty(ctx, driverProperty) + if err != nil { + return 0, err + } + ip, err := b.InstalledPackage(ctx, driver) + if err != nil { + return 0, err + } + return ip.VersionCode, err +} + +// PrepareGpuProfiling implements the adb.Device interface. +func (b *binding) PrepareGpuProfiling(ctx context.Context, installedPackage *android.InstalledPackage) (bool, string, app.Cleanup, error) { + driver, err := b.GraphicsDriver(ctx) + if err != nil { + // If there's an error, keep going to attempt to use GPU profiling + // libraries in system image. + log.W(ctx, "Failed to query developer driver: %v, assuming no developer driver found.", err) + } + + // Setup debug property. The GPU driver must enable profiling and have proper + // instrumentation if the debug property is set to 1. + err = b.SetSystemProperty(ctx, gpuDebugProperty, "1") + if err != nil { + return false, "", nil, err + } + var cleanup app.Cleanup = func(ctx context.Context) { + b.SetSystemProperty(ctx, gpuDebugProperty, "") + } + + if driver.Package != "" { + log.I(ctx, "Using GPU profiling libraries from developer driver package: %v.", driver.Package) + + // Set up device info service to use prerelease driver. + nextCleanup, err := SetupPrereleaseDriver(ctx, b, installedPackage) + cleanup = cleanup.Then(nextCleanup) + if err != nil { + return false, "", cleanup, err + } + return true, driver.Package, cleanup, nil + } + + // For Android 11, GPU profiling could be part of the system driver. It can be implemented as + // a vulkan layer. Hence check whether GPU profiling is supported as part of the system driver, + // and append the vulkan profiling layer apk package name as a place to discover the vulkan + // profiling library. + log.I(ctx, "No developer driver found, attempting to use GPU profiling libraries in system image.") + + supported, err := b.SystemProperty(ctx, systemImageGpuProfilerSupportProperty) + if err != nil { + return false, "", cleanup, err + } + if supported != "true" && supported != "1" { + return false, "", cleanup, nil + } + + packageName, err := b.SystemProperty(ctx, gpuProfilerVulkanLayerApkProperty) + if err != nil { + return false, "", cleanup, err + } + return true, packageName, cleanup, nil +} diff --git a/core/os/android/adb/device.go b/core/os/android/adb/device.go index f2b4f1e9ab..07d925f049 100644 --- a/core/os/android/adb/device.go +++ b/core/os/android/adb/device.go @@ -42,7 +42,11 @@ const ( // Frequency at which to print scan errors. printScanErrorsEveryNSeconds = 120 // Global settings for opting to use prerelease driver. - prereleaseDriverSettingVariable = "game_driver_prerelease_opt_in_apps" + oldDeveloperDriverSettingVariable = "game_driver_prerelease_opt_in_apps" + developerDriverSettingVariable = "updatable_driver_prerelease_opt_in_apps" + + // File that gates ability to access GPU counters on certain Adreno GPUs. + adrenoGpuCounterPath = "/sys/class/kgsl/kgsl-3d0/perfcounter" ) var ( @@ -56,10 +60,23 @@ var ( cache = map[string]*binding{} cacheMutex sync.Mutex // Guards cache. + // devSerial is an Android device serial id. If it is not empty, then device + // scanning will only consider the device with that particular id. + devSerial string + devSerialMutex sync.Mutex + // Registry of all the discovered devices. registry = bind.NewRegistry() ) +// LimitToSerial restricts the device lookup to only scan and operate on the +// device with the given serial id. +func LimitToSerial(serial string) { + devSerialMutex.Lock() + defer devSerialMutex.Unlock() + devSerial = serial +} + // DeviceInfoProvider is a function that adds additional information to a // Device. type DeviceInfoProvider func(ctx context.Context, d Device) error @@ -86,7 +103,7 @@ func Monitor(ctx context.Context, r *bind.Registry, interval time.Duration) erro for { if err := scanDevices(ctx); err != nil { if time.Since(lastErrorPrinted).Seconds() > printScanErrorsEveryNSeconds { - log.E(ctx, "Couldn't scan devices: %v", err) + log.E(ctx, "Couldn't scan Android devices: %v", err) lastErrorPrinted = time.Now() } } else { @@ -107,28 +124,41 @@ func Devices(ctx context.Context) (DeviceList, error) { return nil, err } devs := registry.Devices() - out := make(DeviceList, len(devs)) + deviceList := make(DeviceList, len(devs)) for i, d := range devs { - out[i] = d.(Device) + deviceList[i] = d.(Device) } - return out, nil + return deviceList, nil } func SetupPrereleaseDriver(ctx context.Context, d Device, p *android.InstalledPackage) (app.Cleanup, error) { - oldOptinApps, err := d.SystemSetting(ctx, "global", prereleaseDriverSettingVariable) + settingVariable := developerDriverSettingVariable + if d.Instance().GetConfiguration().GetOS().GetAPIVersion() <= 30 { + settingVariable = oldDeveloperDriverSettingVariable + } + + oldOptinApps, err := d.SystemSetting(ctx, "global", settingVariable) if err != nil { return nil, log.Err(ctx, err, "Failed to get prerelease driver opt in apps.") } + // When key is not in the settings global database, a null will be returned. + // Avoid using it. + if oldOptinApps == "null" { + oldOptinApps = "" + } if strings.Contains(oldOptinApps, p.Name) { return nil, nil } - newOptinApps := oldOptinApps + "," + p.Name + newOptinApps := p.Name + if oldOptinApps != "" { + newOptinApps = oldOptinApps + "," + newOptinApps + } // TODO(b/145893290) Check whether application has developer driver enabled once b/145893290 is fixed. - if err := d.SetSystemSetting(ctx, "global", prereleaseDriverSettingVariable, newOptinApps); err != nil { + if err := d.SetSystemSetting(ctx, "global", settingVariable, newOptinApps); err != nil { return nil, log.Errf(ctx, err, "Failed to set up prerelease driver for app: %v.", p.Name) } return func(ctx context.Context) { - d.SetSystemSetting(ctx, "global", prereleaseDriverSettingVariable, oldOptinApps) + d.SetSystemSetting(ctx, "global", settingVariable, oldOptinApps) }, nil } @@ -150,6 +180,11 @@ func newDevice(ctx context.Context, serial string, status bind.Status) (*binding } } + // Early bail out if we cannot get device information + if d.To.Configuration.Hardware == nil { + return nil, log.Errf(ctx, nil, "Cannot get device information") + } + // Collect the operating system version if version, err := d.SystemProperty(ctx, "ro.build.version.release"); err == nil { var major, minor, point int32 @@ -160,6 +195,13 @@ func newDevice(ctx context.Context, serial string, status bind.Status) (*binding // Collect the API version if version, err := d.SystemProperty(ctx, "ro.build.version.sdk"); err == nil { v, _ := strconv.Atoi(version) + // preview_sdk is used to determine the version for the next OS release + // Until the official release, new OS releases will use the same sdk + // version as the previous OS while setting the preview_sdk + if preview, err := d.SystemProperty(ctx, "ro.build.version.preview_sdk"); err == nil { + p, _ := strconv.Atoi(preview) + v += p + } d.To.Configuration.OS.APIVersion = int32(v) } @@ -184,14 +226,14 @@ func newDevice(ctx context.Context, serial string, status bind.Status) (*binding if seen[abi] { continue } - d.To.Configuration.ABIs = append(d.To.Configuration.ABIs, device.ABIByName(abi)) + d.To.Configuration.ABIs = append(d.To.Configuration.ABIs, device.AndroidABIByName(abi)) seen[abi] = true } } // Make sure Perfetto daemons are running. if err := d.EnsurePerfettoPersistent(ctx); err != nil { - log.W(ctx, "Failed to singal Perfetto services to start", err) + log.W(ctx, "Failed to signal Perfetto services to start", err) } // Run device info providers only if the API is supported @@ -210,37 +252,54 @@ func newDevice(ctx context.Context, serial string, status bind.Status) (*binding d.To.Configuration.PerfettoCapability = perfettoCapability } - // If the VkRenderStagesProducer layer exist, we assume the render stages producer is - // implemented in the layer. - for _, l := range d.To.Configuration.GetDrivers().GetVulkan().GetLayers() { - if l.Name == "VkRenderStagesProducer" { - capability := d.To.Configuration.PerfettoCapability - if capability == nil { - capability = &device.PerfettoCapability{ - GpuProfiling: &device.GPUProfiling{}, + // Query device ANGLE support + if angle, err := d.QueryAngle(ctx); err == nil { + d.To.Configuration.Angle = angle + } + + // Query infos related to the Vulkan driver + if d.To.Configuration.GetDrivers() != nil && d.To.Configuration.GetDrivers().GetVulkan() != nil { + + // If the VkRenderStagesProducer layer exist, we assume the render stages producer is + // implemented in the layer. + for _, l := range d.To.Configuration.GetDrivers().GetVulkan().GetLayers() { + if l.Name == "VkRenderStagesProducer" { + capability := d.To.Configuration.PerfettoCapability + if capability == nil { + capability = &device.PerfettoCapability{ + GpuProfiling: &device.GPUProfiling{}, + } + d.To.Configuration.PerfettoCapability = capability } + gpu := capability.GpuProfiling + gpu.HasRenderStageProducerLayer = true + gpu.HasRenderStage = true + break } - gpu := capability.GpuProfiling - if gpu == nil { - gpu = &device.GPUProfiling{} - capability.GpuProfiling = gpu - } - gpu.HasRenderStageProducerLayer = true - gpu.HasRenderStage = true - break + } + + if version, err := d.DriverVersionCode(ctx); err == nil { + d.To.Configuration.Drivers.Vulkan.Version = strconv.Itoa(version) } } - if d.To.Configuration == nil || - d.To.Configuration.Hardware == nil { - return nil, log.Errf(ctx, nil, "Cannot get device information") + if d.Instance().GetName() == "" { + d.Instance().Name = d.To.Configuration.Hardware.Name } - d.Instance().Name = d.To.Configuration.Hardware.Name if i := d.Instance(); i.ID == nil || allZero(i.ID.Data) { - // Generate an identifier for the device based on it's details. + // Generate an identifier for the device based on its details. i.GenID() } + // Certain Adreno GPUs require an extra step to activate counters. + gpuName := d.Instance().GetConfiguration().GetHardware().GetGPU().GetName() + if strings.Contains(gpuName, "Adreno") { + if err := d.enableAdrenoGpuCounters(ctx); err != nil { + // Only log here instead of returning an error to make sure that device still gets registered + log.E(ctx, "Unable to enable Adreno GPU counters: %v", err) + } + } + return d, nil } @@ -253,7 +312,8 @@ func allZero(bytes []byte) bool { return false } -// scanDevices returns the list of attached Android devices. +// scanDevices returns the list of attached Android devices. It is impacted by +// previous calls to LimitToSerial(). func scanDevices(ctx context.Context) error { exe, err := adb() if err != nil { @@ -272,6 +332,9 @@ func scanDevices(ctx context.Context) error { defer cacheMutex.Unlock() for serial, status := range parsed { + if (devSerial != "") && (serial != devSerial) { + continue + } cached, ok := cache[serial] if !ok || status != cached.Status(ctx) { device, err := newDevice(ctx, serial, status) @@ -286,9 +349,11 @@ func scanDevices(ctx context.Context) error { } } - // Remove cached results for removed devices. + // Remove cached results for removed devices. If we're limited to a single + // serial, make sure to remove any device that doesn't match it. for serial, cached := range cache { - if _, found := parsed[serial]; !found { + notTheSerialDevice := (devSerial != "") && (serial != devSerial) + if _, found := parsed[serial]; !found || notTheSerialDevice { delete(cache, serial) registry.RemoveDevice(ctx, cached) } @@ -319,13 +384,13 @@ func parseDevices(ctx context.Context, out string) (map[string]bind.Status, erro serial, status := fields[0], fields[1] switch status { case "unknown": - devices[serial] = bind.Status_Unknown + devices[serial] = bind.UnknownStatus case "offline": - devices[serial] = bind.Status_Offline + devices[serial] = bind.Offline case "device": - devices[serial] = bind.Status_Online + devices[serial] = bind.Online case "unauthorized": - devices[serial] = bind.Status_Unauthorized + devices[serial] = bind.Unauthorized default: return nil, log.Errf(ctx, ErrInvalidStatus, "value: %v", status) } @@ -359,6 +424,23 @@ func (b *binding) IsLocal(ctx context.Context) (bool, error) { return true, nil } +// Enable GPU counters on Adreno GPUs +func (b *binding) enableAdrenoGpuCounters(ctx context.Context) error { + lsResult, err := b.Shell(fmt.Sprintf("ls %s", adrenoGpuCounterPath)).Call(ctx) + if err != nil { + return fmt.Errorf("Unable to access sysfs node: %v", err) + } + + // If the file does not exist, then counters are enabled by default on this + // Adreno GPU. + if lsResult != adrenoGpuCounterPath { + return nil + } + + _, err = b.Shell(fmt.Sprintf("echo 1 > %s", adrenoGpuCounterPath)).Call(ctx) + return fmt.Errorf("Unable to write to sysfs node: %v", err) +} + var abiToISAs = []struct { abi *device.ABI isa string diff --git a/core/os/android/adb/device_test.go b/core/os/android/adb/device_test.go index 503fb74d2d..3c3e2781e6 100644 --- a/core/os/android/adb/device_test.go +++ b/core/os/android/adb/device_test.go @@ -28,7 +28,7 @@ import ( func TestParseDevices(t_ *testing.T) { ctx := log.Testing(t_) defer func() { devices.Handlers[0] = validDevices }() - expected := &device.Instance{ + deviceInstance := &device.Instance{ Serial: "production_device", Name: "flame", Configuration: &device.Configuration{ @@ -47,15 +47,16 @@ func TestParseDevices(t_ *testing.T) { ABIs: []*device.ABI{device.AndroidARM64v8a}, }, } - expected.GenID() - got, err := adb.Devices(ctx) + deviceInstance.GenID() + adbDevices, err := adb.Devices(ctx) assert.For(ctx, "Normal devices").ThatError(err).Succeeded() - assert.For(ctx, "Normal devices").That(got.FindBySerial(expected.Serial).Instance()).DeepEquals(expected) + matchedDeviceInstance := adbDevices.FindBySerial(deviceInstance.Serial).Instance() + assert.For(ctx, "Normal devices").That(matchedDeviceInstance).DeepEquals(deviceInstance) devices.Handlers[0] = emptyDevices - got, err = adb.Devices(ctx) + adbDevices, err = adb.Devices(ctx) assert.For(ctx, "Empty devices").ThatError(err).Succeeded() - assert.For(ctx, "Empty devices").ThatSlice(got).IsEmpty() + assert.For(ctx, "Empty devices").ThatSlice(adbDevices).IsEmpty() devices.Handlers[0] = invalidDevices _, err = adb.Devices(ctx) @@ -74,3 +75,25 @@ func TestParseDevices(t_ *testing.T) { assert.For(ctx, "not connected").ThatError(err).HasMessage(`Process returned error Cause: Not connected`) } + +func TestLimitToSerial(t_ *testing.T) { + ctx := log.Testing(t_) + + // Make sure to remove the limitation so further tests work fine + defer adb.LimitToSerial("") + + got, err := adb.Devices(ctx) + assert.For(ctx, "No serial limitation").ThatError(err).Succeeded() + assert.For(ctx, "No serial limitation").ThatInteger(len(got)).IsAtLeast(2) + + adb.LimitToSerial("serial_do_match") + got, err = adb.Devices(ctx) + assert.For(ctx, "LimitToSerial do match").ThatError(err).Succeeded() + assert.For(ctx, "LimitToSerial do match").ThatInteger(len(got)).Equals(1) + assert.For(ctx, "LimitToSerial do match").ThatString(got.FindBySerial("serial_do_match").Instance().Serial).Equals("serial_do_match") + + adb.LimitToSerial("serial_do_not_match") + got, err = adb.Devices(ctx) + assert.For(ctx, "LimitToSerial do not match").ThatError(err).Succeeded() + assert.For(ctx, "LimitToSerial do not match").ThatSlice(got).IsEmpty() +} diff --git a/core/os/android/adb/forward.go b/core/os/android/adb/forward.go index 76b46b6ff8..5d54567b97 100644 --- a/core/os/android/adb/forward.go +++ b/core/os/android/adb/forward.go @@ -42,17 +42,18 @@ func (p TCPPort) adbForwardString() string { // LocalFreeTCPPort returns a currently free TCP port on the localhost. // There are two potential issues with using this for ADB port forwarding: -// * There is the potential for the port to be taken between the function -// returning and the port being used by ADB. -// * The system _may_ hold on to the socket after it has been told to close. +// - There is the potential for the port to be taken between the function +// returning and the port being used by ADB. +// - The system _may_ hold on to the socket after it has been told to close. +// // Because of these issues, there is a potential for flakiness. func LocalFreeTCPPort() (TCPPort, error) { - dummy, err := net.Listen("tcp", ":0") + socket, err := net.Listen("tcp", ":0") if err != nil { return 0, err } - defer dummy.Close() - return TCPPort(dummy.Addr().(*net.TCPAddr).Port), nil + defer socket.Close() + return TCPPort(socket.Addr().(*net.TCPAddr).Port), nil } // NamedAbstractSocket represents an abstract UNIX domain socket name on either @@ -73,14 +74,6 @@ func (p NamedFileSystemSocket) adbForwardString() string { return fmt.Sprintf("localfilesystem:%s", p) } -// Jdwp represents a Jdwp process on an Android device. The value is the same as -// the PID of the process. Jdwp implements the Port interface. -type Jdwp int - -func (p Jdwp) adbForwardString() string { - return fmt.Sprintf("jdwp:%d", p) -} - // Forward will forward the specified device Port to the specified local Port. func (b *binding) Forward(ctx context.Context, local, device Port) error { return b.Command("forward", local.adbForwardString(), device.adbForwardString()).Run(ctx) @@ -92,13 +85,3 @@ func (b *binding) RemoveForward(ctx context.Context, local Port) error { ctx = keys.Clone(context.Background(), ctx) return b.Command("forward", "--remove", local.adbForwardString()).Run(ctx) } - -// SetupLocalPort makes sure that the given port can be accessed on localhost -// It returns a new port number to connect to on localhost -func (b *binding) SetupLocalPort(ctx context.Context, port int) (int, error) { - localPort, err := LocalFreeTCPPort() - if err != nil { - return 0, err - } - return int(localPort), b.Forward(ctx, localPort, TCPPort(port)) -} diff --git a/core/os/android/adb/installed_package.go b/core/os/android/adb/installed_package.go index 1080f341c4..b3e05ccb4c 100644 --- a/core/os/android/adb/installed_package.go +++ b/core/os/android/adb/installed_package.go @@ -177,7 +177,7 @@ func (b *binding) parsePackages(str string) (android.InstalledPackages, error) { if splits[1] == "null" { break // This means the package manager will select the platform ABI } - ip.ABI = device.ABIByName(splits[1]) + ip.ABI = device.AndroidABIByName(splits[1]) } } } @@ -211,6 +211,7 @@ func (t *treeNode) find(name string) *treeNode { func parseTabbedTree(str string) *treeNode { head := &treeNode{depth: -1} + extraDepth := 0 for _, line := range strings.Split(str, "\n") { line = strings.TrimRight(line, "\r") if line == "" { @@ -219,14 +220,35 @@ func parseTabbedTree(str string) *treeNode { // Calculate the line's depth depth := 0 - for i, r := range line { + for _, r := range line { if r == ' ' { depth++ } else { - line = line[i:] break } } + line = line[depth:] + + if line == "" { + // A line containing only whitespace probably comes from an extra newline + // character being printed before the intended line. So, add the depth that + // would have resulted from this empty line to the next line. Example with + // front spaces shown as '_': + // ... + // Known Packages: + // _ Package [foo]: + // __ + //Package categories: + // ___ category bar + // ... + // In the above example "Package categories" was meant to be indented by + // two spaces, which are present on the previous line. + extraDepth += depth + continue + } + + depth += extraDepth + extraDepth = 0 // Find the insertion point for { diff --git a/core/os/android/adb/perfetto.go b/core/os/android/adb/perfetto.go index fef00637b7..9ebab5e8a5 100644 --- a/core/os/android/adb/perfetto.go +++ b/core/os/android/adb/perfetto.go @@ -47,7 +47,7 @@ func (b *binding) StartPerfettoTrace(ctx context.Context, config *perfetto_pb.Tr reader, stdout := io.Pipe() logRing := ring.New(10) // TODO(apbodnar) Find a way to reliably know when Perfetto/producers are ready (b/147388497) - readyOnce := task.Once(task.Delay(ready, 250*time.Millisecond)) + readyOnce := task.Once(task.Delay(ready, 1000*time.Millisecond)) data, err := proto.Marshal(config) if err != nil { return err diff --git a/core/os/android/adb/screen.go b/core/os/android/adb/screen.go index bd3d3f6d89..26fa8809e9 100644 --- a/core/os/android/adb/screen.go +++ b/core/os/android/adb/screen.go @@ -105,18 +105,17 @@ func (b *binding) UnlockScreen(ctx context.Context) (bool, error) { // Devices may do unexpected transitions between screen // states, so this code does not try to be smart about // expected state changes: unless screenOnUnlocked, apply the - // wakeup (put screen on) and menu (unlock screen if no - // credentials required) key event sequence. + // wakeup (put screen on) and dismiss-keyguard (unlock screen + // if no credentials required) sequence. if err := b.KeyEvent(ctx, android.KeyCode_Wakeup); err != nil { return false, err } - if err := b.KeyEvent(ctx, android.KeyCode_Menu); err != nil { + if err := b.Shell("wm", "dismiss-keyguard").Run(ctx); err != nil { return false, err } - // A sleep here is necessary to make sure the key events had - // enough time to affect the screen state. + // A sleep here is necessary to make sure the above commands had enough + // time to affect the screen state. time.Sleep(keyEventEffectDelay) return b.isScreenUnlocked(ctx) } - return false, log.Err(ctx, ErrScreenState, "") } diff --git a/core/os/android/apk/BUILD.bazel b/core/os/android/apk/BUILD.bazel index 35a436f976..7891fd45c8 100644 --- a/core/os/android/apk/BUILD.bazel +++ b/core/os/android/apk/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", diff --git a/core/os/android/apk/apk.go b/core/os/android/apk/apk.go index 737b3258a5..411ba9474b 100644 --- a/core/os/android/apk/apk.go +++ b/core/os/android/apk/apk.go @@ -85,7 +85,7 @@ func GatherABIs(files []*zip.File) []*device.ABI { if len(parts) >= 2 && parts[0] == "lib" { abiName := parts[1] if _, existing := seen[abiName]; !existing { - abis = append(abis, device.ABIByName(abiName)) + abis = append(abis, device.AndroidABIByName(abiName)) seen[abiName] = struct{}{} } } diff --git a/core/os/android/device.go b/core/os/android/device.go index 715dccdb4d..129998d1b3 100644 --- a/core/os/android/device.go +++ b/core/os/android/device.go @@ -29,7 +29,7 @@ import ( // Device extends the bind.Device interface with capabilities specific to android devices. type Device interface { - bind.Device + bind.DeviceWithShell // InstallAPK installs the specified APK to the device. If reinstall is true // and the package is already installed on the device then it will be replaced. InstallAPK(ctx context.Context, path string, reinstall bool, grantPermissions bool) error @@ -49,6 +49,8 @@ type Device interface { Push(ctx context.Context, local, remote string) error // Pulls the remote file to the local one. Pull(ctx context.Context, remote, local string) error + // RemoveFile removes the given file from the device + RemoveFile(ctx context.Context, path string) error // KeyEvent simulates a key-event on the device. KeyEvent(ctx context.Context, key KeyCode) error // SendEvent simulates low-level user-input to the device. @@ -90,6 +92,8 @@ type Device interface { DeleteSystemSetting(ctx context.Context, namespace, key string) error // StartPerfettoTrace starts a perfetto trace. StartPerfettoTrace(ctx context.Context, config *perfetto_pb.TraceConfig, out string, stop task.Signal, ready task.Task) error + // SupportsAngle returns true if this device will work with ANGLE + SupportsAngle(ctx context.Context) bool } // LogcatMessage represents a single logcat message. diff --git a/core/os/android/layers.go b/core/os/android/layers.go index 588f413dac..bb31476e7d 100644 --- a/core/os/android/layers.go +++ b/core/os/android/layers.go @@ -22,20 +22,6 @@ import ( "github.com/google/gapid/core/log" ) -const eglLayersExt = "EGL_ANDROID_GLES_layers" - -// SupportsGLESLayersViaSystemSettings returns whether the given device supports -// loading GLES layers via the system settings. -func SupportsGLESLayersViaSystemSettings(d Device) bool { - exts := d.Instance().GetConfiguration().GetDrivers().GetOpengl().GetExtensions() - for _, ext := range exts { - if ext == eglLayersExt { - return true - } - } - return false -} - // SupportsVulkanLayersViaSystemSettings returns whether the given device supports // loading Vulkan layers via the system settings. func SupportsVulkanLayersViaSystemSettings(d Device) bool { @@ -44,10 +30,10 @@ func SupportsVulkanLayersViaSystemSettings(d Device) bool { return apiVersion >= 28 } -// SetupLayer initializes d to use either a Vulkan or GLES layer from layerPkgs +// SetupLayers initializes d to use Vulkan layers from layerPkgs // limited to the app with package appPkg using the system settings and returns // a cleanup to remove the layer settings. -func SetupLayers(ctx context.Context, d Device, appPkg string, layerPkgs []string, layers []string, vulkan bool) (app.Cleanup, error) { +func SetupLayers(ctx context.Context, d Device, appPkg string, layerPkgs []string, layers []string) (app.Cleanup, error) { var cleanup app.Cleanup // pushSetting changes a device property for the duration of the trace. pushSetting := func(ns, key, val string) error { @@ -68,21 +54,11 @@ func SetupLayers(ctx context.Context, d Device, appPkg string, layerPkgs []strin return cleanup.Invoke(ctx), err } if len(layers) > 0 { - if vulkan { - if err := pushSetting("global", "gpu_debug_layers", "\""+strings.Join(layers, ":")+"\""); err != nil { - return cleanup.Invoke(ctx), err - } - } else { - if err := pushSetting("global", "gpu_debug_layers_gles", "\""+strings.Join(layers, ":")+"\""); err != nil { - return cleanup.Invoke(ctx), err - } + if err := pushSetting("global", "gpu_debug_layers", "\""+strings.Join(layers, ":")+"\""); err != nil { + return cleanup.Invoke(ctx), err } } else { - if vulkan { - d.DeleteSystemSetting(ctx, "global", "gpu_debug_layers") - } else { - d.DeleteSystemSetting(ctx, "global", "gpu_debug_layers_gles") - } + d.DeleteSystemSetting(ctx, "global", "gpu_debug_layers") } return cleanup, nil diff --git a/core/os/android/manifest/BUILD.bazel b/core/os/android/manifest/BUILD.bazel index 3007be839b..c116341671 100644 --- a/core/os/android/manifest/BUILD.bazel +++ b/core/os/android/manifest/BUILD.bazel @@ -32,8 +32,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["manifest_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/os/device/BUILD.bazel b/core/os/device/BUILD.bazel index 398b654f17..7d7202b903 100644 --- a/core/os/device/BUILD.bazel +++ b/core/os/device/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", @@ -81,8 +82,8 @@ go_test( "linux_test.go", "osx_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", "//core/os/device/host:go_default_library", diff --git a/core/os/device/abi.go b/core/os/device/abi.go index 79c0f81449..7a8b023703 100644 --- a/core/os/device/abi.go +++ b/core/os/device/abi.go @@ -14,6 +14,10 @@ package device +import ( + "strings" +) + var ( UnknownABI = abi("unknown", UnknownOS, UnknownArchitecture, &MemoryLayout{}) // Keep this one in, some applications incorrectly advertise arm, when they mean armv7 @@ -28,11 +32,14 @@ var ( LinuxX86_64 = abi("linux_x64", Linux, X86_64, Little64) OSXX86_64 = abi("osx_x64", OSX, X86_64, Little64) WindowsX86_64 = abi("windows_x64", Windows, X86_64, Little64) - StadiaX86_64 = abi("stadia", Stadia, X86_64, Little64) + + FuchsiaARM64 = abi("aarch64", Fuchsia, ARMv8a, ARM64v8aLayout) ) -var abiByName = map[string]*ABI{} +var androidAbiByName = map[string]*ABI{} +// abi is the private helper constructor used by the constants above. It also +// registers the Android ABIs in a map for AndroidABIByName. func abi(name string, os OSKind, arch Architecture, ml *MemoryLayout) *ABI { abi := &ABI{ Name: name, @@ -40,15 +47,21 @@ func abi(name string, os OSKind, arch Architecture, ml *MemoryLayout) *ABI { Architecture: arch, MemoryLayout: ml, } - abiByName[name] = abi + + if os == Android { + if _, ok := androidAbiByName[name]; ok { + panic("Duplicate Android ABI name: " + name) + } + androidAbiByName[name] = abi + } return abi } -// ABIByName returns the ABI that matches the provided human name. +// AndroidABIByName returns the Android ABI that matches the provided human name. // If there is no standard ABI that matches the name, then the returned ABI will have an UnknownOS and // UnknownArchitecture. -func ABIByName(name string) *ABI { - abi, ok := abiByName[name] +func AndroidABIByName(name string) *ABI { + abi, ok := androidAbiByName[name] if !ok { abi = &ABI{ Name: name, @@ -95,8 +108,12 @@ func (c *Configuration) PreferredABI(abis []*ABI) *ABI { } } } - if len(c.ABIs) == 0 { - return UnknownABI + + // Return the first ABI that is not a hardware ASAN ABI. + for _, abi := range c.ABIs { + if !strings.HasSuffix(abi.Name, "-hwasan") { + return abi + } } - return c.ABIs[0] + return UnknownABI } diff --git a/core/os/device/abi_test.go b/core/os/device/abi_test.go index e60e1df0c7..a88bd85180 100644 --- a/core/os/device/abi_test.go +++ b/core/os/device/abi_test.go @@ -21,9 +21,9 @@ import ( "github.com/google/gapid/core/os/device" ) -func TestABIByName(t *testing.T) { +func TestAndroidABIByName(t *testing.T) { assert := assert.To(t) - abi := device.ABIByName("invalid") + abi := device.AndroidABIByName("invalid") assert.For("ABI.Name").That(abi.Name).Equals("invalid") assert.For("ABI.Architecture").That(abi.Architecture).Equals(device.UnknownArchitecture) assert.For("ABI.OS").That(abi.OS).Equals(device.UnknownOS) diff --git a/core/os/device/android.go b/core/os/device/android.go index 871231a4da..44353076e6 100644 --- a/core/os/device/android.go +++ b/core/os/device/android.go @@ -27,6 +27,8 @@ func AndroidOS(major, minor, point int32) *OS { PointVersion: point, } switch { + case major == 11: + os.Name = "Android 11" case major == 10: os.Name = "Android 10" case major == 9: diff --git a/core/os/device/bind/BUILD.bazel b/core/os/device/bind/BUILD.bazel index 4e5386c4f8..307163b3b8 100644 --- a/core/os/device/bind/BUILD.bazel +++ b/core/os/device/bind/BUILD.bazel @@ -12,28 +12,26 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ "bind.go", + "bind_posix.go", + "bind_windows.go", + "desktop.go", "device.go", "doc.go", "registry.go", "simple.go", - "simple_posix.go", - "simple_windows.go", ], - embed = [":bind_go_proto"], importpath = "github.com/google/gapid/core/os/device/bind", visibility = ["//visibility:public"], deps = [ "//core/app:go_default_library", "//core/context/keys:go_default_library", "//core/data/id:go_default_library", - "//core/fault:go_default_library", "//core/log:go_default_library", "//core/os/device:go_default_library", "//core/os/device/host:go_default_library", @@ -41,16 +39,3 @@ go_library( "//gapis/perfetto:go_default_library", ], ) - -proto_library( - name = "bind_proto", - srcs = ["bind.proto"], - visibility = ["//visibility:public"], -) - -go_proto_library( - name = "bind_go_proto", - importpath = "github.com/google/gapid/core/os/device/bind", - proto = ":bind_proto", - visibility = ["//visibility:public"], -) diff --git a/core/os/device/bind/bind.go b/core/os/device/bind/bind.go index 1acd44e382..6ec5bb0a0c 100644 --- a/core/os/device/bind/bind.go +++ b/core/os/device/bind/bind.go @@ -16,24 +16,160 @@ package bind import ( "context" + "io" + "io/ioutil" + "os" "sync" + "github.com/google/gapid/core/app" "github.com/google/gapid/core/os/device/host" + "github.com/google/gapid/core/os/shell" ) +type binding struct { + Simple +} + var ( hostMutex sync.Mutex - hostDev Device + hostDev Desktop ) // Host returns the Device to the host. -func Host(ctx context.Context) Device { +func Host(ctx context.Context) Desktop { hostMutex.Lock() defer hostMutex.Unlock() if hostDev == nil { - hostDev = &Simple{ - To: host.Instance(ctx), + hostDev = &binding{ + Simple{ + To: host.Instance(ctx), + }, } } return hostDev } + +// Shell implements the Device interface returning commands that will error if run. +func (b *binding) Shell(name string, args ...string) shell.Cmd { + return shell.Command(name, args...).On(hostTarget{}).Verbose() +} + +// TempFile creates a temporary file on the given Device. It returns the +// path to the file, and a function that can be called to clean it up. +func (b *binding) TempFile(ctx context.Context) (string, func(ctx context.Context), error) { + fl, e := ioutil.TempFile("", "") + if e != nil { + return "", nil, e + } + + f := fl.Name() + fl.Close() + return f, func(ctx context.Context) { + os.Remove(f) + }, nil +} + +// FileContents returns the contents of a given file on the Device. +func (b *binding) FileContents(ctx context.Context, path string) (string, error) { + contents, err := ioutil.ReadFile(path) + if err != nil { + return "", err + } + return string(contents), nil +} + +func (b *binding) PushFile(ctx context.Context, sourcePath, destPath string) error { + in, err := os.Open(sourcePath) + if err != nil { + return err + } + defer in.Close() + return b.WriteFile(ctx, in, 0666, destPath) +} + +func (b *binding) TempDir(ctx context.Context) (string, app.Cleanup, error) { + fl, e := ioutil.TempDir("", "") + if e != nil { + return "", nil, e + } + + return fl, func(ctx context.Context) { + os.RemoveAll(fl) + }, nil +} + +func (b *binding) WriteFile(ctx context.Context, contents io.Reader, mode os.FileMode, destPath string) error { + out, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE, mode) + if err != nil { + return err + } + defer out.Close() + _, err = io.Copy(out, contents) + if err != nil { + return err + } + return out.Close() +} + +// RemoveFile removes the given file from the device +func (b *binding) RemoveFile(ctx context.Context, path string) error { + return os.Remove(path) +} + +// GetEnv returns the default environment for the Device. +func (b *binding) GetEnv(ctx context.Context) (*shell.Env, error) { + return shell.CloneEnv(), nil +} + +// SetupLocalPort makes sure that the given port can be accessed on localhost +// It returns a new port number to connect to on localhost +func (b *binding) SetupLocalPort(ctx context.Context, port int) (int, error) { + return port, nil +} + +// IsFile returns true if the given path refers to a file +func (b *binding) IsFile(ctx context.Context, path string) (bool, error) { + if path == "" { + return false, nil + } + + f, err := os.Stat(path) + if err != nil { + return false, err + } + if f.Mode().IsRegular() || (f.Mode()&(os.ModeNamedPipe|os.ModeSocket)) != 0 { + return true, nil + } + return false, nil +} + +// IsDirectory returns true if the given path refers to a directory +func (b *binding) IsDirectory(ctx context.Context, path string) (bool, error) { + if path == "" { + return true, nil + } + + f, err := os.Stat(path) + if err != nil { + return false, err + } + if f.Mode().IsDir() { + return true, nil + } + return false, nil +} + +// GetWorkingDirectory returns the directory that this device considers CWD +func (b *binding) GetWorkingDirectory(ctx context.Context) (string, error) { + return os.Getwd() +} + +func (b *binding) IsLocal(ctx context.Context) (bool, error) { + return true, nil +} + +type hostTarget struct{} + +func (t hostTarget) Start(cmd shell.Cmd) (shell.Process, error) { + return shell.LocalTarget.Start(cmd) +} diff --git a/core/os/device/bind/bind.proto b/core/os/device/bind/bind.proto deleted file mode 100644 index 379fe6c6a2..0000000000 --- a/core/os/device/bind/bind.proto +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -syntax = "proto3"; - -package bind; -option go_package = "github.com/google/gapid/core/os/device/bind"; - -// Status represents the last queried connection status of a device. -enum Status { - Unknown = 0; - Offline = 1; - Online = 2; - Unauthorized = 3; -} diff --git a/core/os/device/bind/bind_posix.go b/core/os/device/bind/bind_posix.go new file mode 100644 index 0000000000..6874b5e2c2 --- /dev/null +++ b/core/os/device/bind/bind_posix.go @@ -0,0 +1,93 @@ +// Copyright (C) 2017 Google Inc. +// +// 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. + +//go:build !windows + +package bind + +import ( + "context" + "fmt" + "io/ioutil" + "net" + "path/filepath" + + "github.com/google/gapid/gapis/perfetto" +) + +const ( + perfettoSocket = "/tmp/perfetto-consumer" +) + +// ListExecutables returns the executables in a particular directory as given by path +func (b *binding) ListExecutables(ctx context.Context, path string) ([]string, error) { + if path == "" { + path = "/" + } + rets := []string{} + infos, err := ioutil.ReadDir(path) + if err != nil { + return rets, nil + } + for _, inf := range infos { + if inf.Mode().IsRegular() && (inf.Mode()&0500) == 0500 { + rets = append(rets, inf.Name()) + } + } + return rets, nil +} + +// GetURIRoot returns the root URI for the entire system +func (b *binding) GetURIRoot() string { + return "/" +} + +// ListDirectories returns a list of directories rooted at a particular path +func (b *binding) ListDirectories(ctx context.Context, path string) ([]string, error) { + rets := []string{} + infos, err := ioutil.ReadDir(path) + if err != nil { + return rets, nil + } + for _, inf := range infos { + if inf.Mode().IsDir() { + if _, err := ioutil.ReadDir(filepath.Join(path, inf.Name())); err == nil { + rets = append(rets, inf.Name()) + } + } + } + return rets, nil +} + +// SupportsPerfetto returns true if the given device supports taking a +// Perfetto trace. +func (b *binding) SupportsPerfetto(ctx context.Context) bool { + if support, err := b.IsFile(ctx, perfettoSocket); err == nil { + return support + } + return false +} + +// ConnectPerfetto connects to a Perfetto service running on this device +// and returns an open socket connection to the service. +func (b *binding) ConnectPerfetto(ctx context.Context) (*perfetto.Client, error) { + if !b.SupportsPerfetto(ctx) { + return nil, fmt.Errorf("Perfetto is not supported on this device") + } + conn, err := net.Dial("unix", perfettoSocket) + if err != nil { + return nil, err + } + return perfetto.NewClient(ctx, conn, nil) +} diff --git a/core/os/device/bind/bind_windows.go b/core/os/device/bind/bind_windows.go new file mode 100644 index 0000000000..3515b2084b --- /dev/null +++ b/core/os/device/bind/bind_windows.go @@ -0,0 +1,116 @@ +// Copyright (C) 2017 Google Inc. +// +// 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. + +//go:build windows + +package bind + +import ( + "context" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + "syscall" + "unsafe" +) + +// ListExecutables returns the executables in a particular directory as given by path +func (b *binding) ListExecutables(ctx context.Context, path string) ([]string, error) { + rets := []string{} + if path == "" { + return rets, nil + } + infos, err := ioutil.ReadDir(path) + if err != nil { + return rets, nil + } + for _, inf := range infos { + if strings.HasSuffix(inf.Name(), ".exe") { + rets = append(rets, inf.Name()) + } + } + return rets, nil +} + +func (b *binding) drives(ctx context.Context) ([]string, error) { + drives := []string{} + + kernel32, err := syscall.LoadDLL("kernel32.dll") + if err != nil { + return drives, err + } + + defer kernel32.Release() + + getDrives, err := kernel32.FindProc("GetLogicalDriveStringsW") + if err != nil { + return drives, err + } + + l, _, _ := getDrives.Call(uintptr(0), uintptr(unsafe.Pointer(nil))) + + buff := make([]uint16, l) + bs := uint32(l) + + hr, _, _ := getDrives.Call(uintptr(bs), uintptr(unsafe.Pointer(&buff[0]))) + + if hr == 0 { + return nil, fmt.Errorf("Could not get drive listing") + } + + accumulation := []uint16{} + for i := 0; i < int(hr); i++ { + v := buff[i] + if v == 0 { + if len(accumulation) == 0 { + return nil, fmt.Errorf("Unhandled drive listing output") + } + drives = append(drives, syscall.UTF16ToString(accumulation)) + accumulation = []uint16{} + } else { + accumulation = append(accumulation, v) + } + } + if len(accumulation) > 0 { + drives = append(drives, syscall.UTF16ToString(accumulation)) + } + + return drives, nil +} + +// GetURIRoot returns the root URI for the entire system +func (b *binding) GetURIRoot() string { + return "" +} + +// ListDirectories returns a list of directories rooted at a particular path +func (b *binding) ListDirectories(ctx context.Context, path string) ([]string, error) { + if path == "" { + return b.drives(ctx) + } + rets := []string{} + infos, err := ioutil.ReadDir(path) + if err != nil { + return rets, nil + } + for _, inf := range infos { + if inf.Mode().IsDir() { + if _, err := ioutil.ReadDir(filepath.Join(path, inf.Name())); err == nil { + rets = append(rets, inf.Name()) + } + } + } + return rets, nil +} diff --git a/core/os/device/bind/desktop.go b/core/os/device/bind/desktop.go new file mode 100644 index 0000000000..e1a047a895 --- /dev/null +++ b/core/os/device/bind/desktop.go @@ -0,0 +1,63 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package bind + +import ( + "context" + "io" + "os" + + "github.com/google/gapid/core/app" + "github.com/google/gapid/core/os/shell" +) + +// Desktop represents a desktop-like device, either the local host, or remote. +type Desktop interface { + DeviceWithShell + // SetupLocalPort makes sure that the given port can be accessed on localhost + // It returns a new port number to connect to on localhost + SetupLocalPort(ctx context.Context, port int) (int, error) + // TempFile creates a temporary file on the given Device. It returns the + // path to the file, and a function that can be called to clean it up. + TempFile(ctx context.Context) (string, func(ctx context.Context), error) + // TempDir makes a temporary directory, and returns the + // path, as well as a function to call to clean it up. + TempDir(ctx context.Context) (string, app.Cleanup, error) + // FileContents returns the contents of a given file on the Device. + FileContents(ctx context.Context, path string) (string, error) + // RemoveFile removes the given file from the device + RemoveFile(ctx context.Context, path string) error + // GetEnv returns the default environment for the Device. + GetEnv(ctx context.Context) (*shell.Env, error) + // ListExecutables returns the executables in a particular directory as given by path + ListExecutables(ctx context.Context, path string) ([]string, error) + // ListDirectories returns a list of directories rooted at a particular path + ListDirectories(ctx context.Context, path string) ([]string, error) + // GetURIRoot returns the root URI for the entire system + GetURIRoot() string + // IsFile returns true if the given path is a file + IsFile(ctx context.Context, path string) (bool, error) + // IsDirectory returns true if the given path is a directory + IsDirectory(ctx context.Context, path string) (bool, error) + // GetWorkingDirectory returns the directory that this device considers CWD + GetWorkingDirectory(ctx context.Context) (string, error) + // IsLocal returns true if this tracer is local + IsLocal(ctx context.Context) (bool, error) + // PushFile will transfer the local file at sourcePath to the remote + // machine at destPath + PushFile(ctx context.Context, sourcePath, destPath string) error + // WriteFile writes the given file into the given location on the remote device + WriteFile(ctx context.Context, contents io.Reader, mode os.FileMode, destPath string) error +} diff --git a/core/os/device/bind/device.go b/core/os/device/bind/device.go index 9a11c1699a..2714d22d5b 100644 --- a/core/os/device/bind/device.go +++ b/core/os/device/bind/device.go @@ -16,52 +16,27 @@ package bind import ( "context" - "io" - "os" - "github.com/google/gapid/core/app" "github.com/google/gapid/core/os/device" "github.com/google/gapid/core/os/shell" "github.com/google/gapid/gapis/perfetto" ) +type Status int + +const ( + UnknownStatus Status = iota + Offline + Online + Unauthorized +) + // Device represents a connection to an attached device. type Device interface { // Instance returns the instance information for this device. Instance() *device.Instance // State returns the last known connected status of the device. Status(ctx context.Context) Status - // Shell is a helper that builds a shell.Cmd with d.ShellTarget() as its target - Shell(name string, args ...string) shell.Cmd - // TempFile creates a temporary file on the given Device. It returns the - // path to the file, and a function that can be called to clean it up. - TempFile(ctx context.Context) (string, func(ctx context.Context), error) - // TempDir makes a temporary directory, and returns the - // path, as well as a function to call to clean it up. - TempDir(ctx context.Context) (string, app.Cleanup, error) - // FileContents returns the contents of a given file on the Device. - FileContents(ctx context.Context, path string) (string, error) - // RemoveFile removes the given file from the device - RemoveFile(ctx context.Context, path string) error - // GetEnv returns the default environment for the Device. - GetEnv(ctx context.Context) (*shell.Env, error) - // SetupLocalPort makes sure that the given port can be accessed on localhost - // It returns a new port number to connect to on localhost - SetupLocalPort(ctx context.Context, port int) (int, error) - // ListExecutables returns the executables in a particular directory as given by path - ListExecutables(ctx context.Context, path string) ([]string, error) - // ListDirectories returns a list of directories rooted at a particular path - ListDirectories(ctx context.Context, path string) ([]string, error) - // GetURIRoot returns the root URI for the entire system - GetURIRoot() string - // IsFile returns true if the given path is a file - IsFile(ctx context.Context, path string) (bool, error) - // IsDirectory returns true if the given path is a directory - IsDirectory(ctx context.Context, path string) (bool, error) - // GetWorkingDirectory returns the directory that this device considers CWD - GetWorkingDirectory(ctx context.Context) (string, error) - // IsLocal returns true if this tracer is local - IsLocal(ctx context.Context) (bool, error) // CanTrace returns true if this device can be used to take a trace CanTrace() bool // SupportsPerfetto returns true if this device will work with perfetto @@ -69,9 +44,12 @@ type Device interface { // ConnectPerfetto connects to a Perfetto service running on this device // and returns an open socket connection to the service. ConnectPerfetto(ctx context.Context) (*perfetto.Client, error) - // PushFile will transfer the local file at sourcePath to the remote - // machine at destPath - PushFile(ctx context.Context, sourcePath, destPath string) error - // WriteFile writes the given file into the given location on the remote device - WriteFile(ctx context.Context, contents io.Reader, mode os.FileMode, destPath string) error + // InstallApp installs the given application to this device + InstallApp(ctx context.Context, app string) error +} + +type DeviceWithShell interface { + Device + // Shell is a helper that builds a shell.Cmd with d.ShellTarget() as its target + Shell(name string, args ...string) shell.Cmd } diff --git a/core/os/device/bind/simple.go b/core/os/device/bind/simple.go index 44cf1d20e9..a23056a6d9 100644 --- a/core/os/device/bind/simple.go +++ b/core/os/device/bind/simple.go @@ -16,14 +16,11 @@ package bind import ( "context" - "io" - "io/ioutil" - "os" + "errors" + "fmt" - "github.com/google/gapid/core/app" - "github.com/google/gapid/core/fault" "github.com/google/gapid/core/os/device" - "github.com/google/gapid/core/os/shell" + "github.com/google/gapid/gapis/perfetto" ) // Simple is a very short implementation of the Device interface. @@ -34,11 +31,6 @@ type Simple struct { LastStatus Status } -const ( - // ErrShellNotSupported may be returned by Start if the target does not support a shell. - ErrShellNotSupported = fault.Const("bind.Simple does not support shell commands") -) - func (b *Simple) String() string { if len(b.To.Name) > 0 { return b.To.Name @@ -55,123 +47,15 @@ func (b *Simple) Instance() *device.Instance { return b.To } // Status implements the Device interface returning the Status from the LastStatus field. func (b *Simple) Status(ctx context.Context) Status { return b.LastStatus } -// Shell implements the Device interface returning commands that will error if run. -func (b *Simple) Shell(name string, args ...string) shell.Cmd { - return shell.Command(name, args...).On(simpleTarget{}).Verbose() +// SupportsPerfetto returns true if this device will work with perfetto. +func (b *Simple) SupportsPerfetto(ctx context.Context) bool { + return false } -// TempFile creates a temporary file on the given Device. It returns the -// path to the file, and a function that can be called to clean it up. -func (b *Simple) TempFile(ctx context.Context) (string, func(ctx context.Context), error) { - fl, e := ioutil.TempFile("", "") - if e != nil { - return "", nil, e - } - - f := fl.Name() - fl.Close() - return f, func(ctx context.Context) { - os.Remove(f) - }, nil -} - -// FileContents returns the contents of a given file on the Device. -func (b *Simple) FileContents(ctx context.Context, path string) (string, error) { - contents, err := ioutil.ReadFile(path) - if err != nil { - return "", err - } - return string(contents), nil -} - -func (b *Simple) PushFile(ctx context.Context, sourcePath, destPath string) error { - in, err := os.Open(sourcePath) - if err != nil { - return err - } - defer in.Close() - return b.WriteFile(ctx, in, 0666, destPath) -} - -func (b *Simple) TempDir(ctx context.Context) (string, app.Cleanup, error) { - fl, e := ioutil.TempDir("", "") - if e != nil { - return "", nil, e - } - - return fl, func(ctx context.Context) { - os.RemoveAll(fl) - }, nil -} - -func (b *Simple) WriteFile(ctx context.Context, contents io.Reader, mode os.FileMode, destPath string) error { - out, err := os.OpenFile(destPath, os.O_RDWR|os.O_CREATE, mode) - if err != nil { - return err - } - defer out.Close() - _, err = io.Copy(out, contents) - if err != nil { - return err - } - return out.Close() -} - -// RemoveFile removes the given file from the device -func (b *Simple) RemoveFile(ctx context.Context, path string) error { - return os.Remove(path) -} - -// GetEnv returns the default environment for the Device. -func (b *Simple) GetEnv(ctx context.Context) (*shell.Env, error) { - return shell.CloneEnv(), nil -} - -// SetupLocalPort makes sure that the given port can be accessed on localhost -// It returns a new port number to connect to on localhost -func (b *Simple) SetupLocalPort(ctx context.Context, port int) (int, error) { - return port, nil -} - -// IsFile returns true if the given path refers to a file -func (b *Simple) IsFile(ctx context.Context, path string) (bool, error) { - if path == "" { - return false, nil - } - - f, err := os.Stat(path) - if err != nil { - return false, err - } - if f.Mode().IsRegular() || (f.Mode()&(os.ModeNamedPipe|os.ModeSocket)) != 0 { - return true, nil - } - return false, nil -} - -// IsDirectory returns true if the given path refers to a directory -func (b *Simple) IsDirectory(ctx context.Context, path string) (bool, error) { - if path == "" { - return true, nil - } - - f, err := os.Stat(path) - if err != nil { - return false, err - } - if f.Mode().IsDir() { - return true, nil - } - return false, nil -} - -// GetWorkingDirectory returns the directory that this device considers CWD -func (b *Simple) GetWorkingDirectory(ctx context.Context) (string, error) { - return os.Getwd() -} - -func (b *Simple) IsLocal(ctx context.Context) (bool, error) { - return true, nil +// ConnectPerfetto connects to a Perfetto service running on this device +// and returns an open socket connection to the service. +func (b *Simple) ConnectPerfetto(ctx context.Context) (*perfetto.Client, error) { + return nil, fmt.Errorf("Perfetto is not supported on this device") } // ABI implements the Device interface returning the first ABI from the Information, or UnknownABI if it has none. @@ -182,8 +66,7 @@ func (b *Simple) ABI() *device.ABI { return b.To.Configuration.ABIs[0] } -type simpleTarget struct{} - -func (t simpleTarget) Start(cmd shell.Cmd) (shell.Process, error) { - return shell.LocalTarget.Start(cmd) +// InstallApp implements the Device interface, always returning an error. +func (b *Simple) InstallApp(ctx context.Context, app string) error { + return errors.New("Installing applications is not supported on this device") } diff --git a/core/os/device/bind/simple_posix.go b/core/os/device/bind/simple_posix.go deleted file mode 100644 index 01100e1a2b..0000000000 --- a/core/os/device/bind/simple_posix.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// +build !windows - -package bind - -import ( - "context" - "fmt" - "io/ioutil" - "net" - "path/filepath" - - "github.com/google/gapid/gapis/perfetto" -) - -const ( - perfettoSocket = "/tmp/perfetto-consumer" -) - -// ListExecutables returns the executables in a particular directory as given by path -func (b *Simple) ListExecutables(ctx context.Context, path string) ([]string, error) { - if path == "" { - path = "/" - } - rets := []string{} - infos, err := ioutil.ReadDir(path) - if err != nil { - return rets, nil - } - for _, inf := range infos { - if inf.Mode().IsRegular() && (inf.Mode()&0500) == 0500 { - rets = append(rets, inf.Name()) - } - } - return rets, nil -} - -// GetURIRoot returns the root URI for the entire system -func (b *Simple) GetURIRoot() string { - return "/" -} - -// ListDirectories returns a list of directories rooted at a particular path -func (b *Simple) ListDirectories(ctx context.Context, path string) ([]string, error) { - rets := []string{} - infos, err := ioutil.ReadDir(path) - if err != nil { - return rets, nil - } - for _, inf := range infos { - if inf.Mode().IsDir() { - if _, err := ioutil.ReadDir(filepath.Join(path, inf.Name())); err == nil { - rets = append(rets, inf.Name()) - } - } - } - return rets, nil -} - -// SupportsPerfetto returns true if the given device supports taking a -// Perfetto trace. -func (b *Simple) SupportsPerfetto(ctx context.Context) bool { - if support, err := b.IsFile(ctx, perfettoSocket); err == nil { - return support - } - return false -} - -// ConnectPerfetto connects to a Perfetto service running on this device -// and returns an open socket connection to the service. -func (b *Simple) ConnectPerfetto(ctx context.Context) (*perfetto.Client, error) { - if !b.SupportsPerfetto(ctx) { - return nil, fmt.Errorf("Perfetto is not supported on this device") - } - conn, err := net.Dial("unix", perfettoSocket) - if err != nil { - return nil, err - } - return perfetto.NewClient(ctx, conn, nil) -} diff --git a/core/os/device/bind/simple_windows.go b/core/os/device/bind/simple_windows.go deleted file mode 100644 index dfb143e165..0000000000 --- a/core/os/device/bind/simple_windows.go +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -// +build windows - -package bind - -import ( - "context" - "fmt" - "io/ioutil" - "path/filepath" - "strings" - "syscall" - "unsafe" - - "github.com/google/gapid/gapis/perfetto" -) - -// ListExecutables returns the executables in a particular directory as given by path -func (b *Simple) ListExecutables(ctx context.Context, path string) ([]string, error) { - rets := []string{} - if path == "" { - return rets, nil - } - infos, err := ioutil.ReadDir(path) - if err != nil { - return rets, nil - } - for _, inf := range infos { - if strings.HasSuffix(inf.Name(), ".exe") { - rets = append(rets, inf.Name()) - } - } - return rets, nil -} - -func (b *Simple) drives(ctx context.Context) ([]string, error) { - drives := []string{} - - kernel32, err := syscall.LoadDLL("kernel32.dll") - if err != nil { - return drives, err - } - - defer kernel32.Release() - - getDrives, err := kernel32.FindProc("GetLogicalDriveStringsW") - if err != nil { - return drives, err - } - - l, _, _ := getDrives.Call(uintptr(0), uintptr(unsafe.Pointer(nil))) - - buff := make([]uint16, l) - bs := uint32(l) - - hr, _, _ := getDrives.Call(uintptr(bs), uintptr(unsafe.Pointer(&buff[0]))) - - if hr == 0 { - return nil, fmt.Errorf("Could not get drive listing") - } - - accumulation := []uint16{} - for i := 0; i < int(hr); i++ { - v := buff[i] - if v == 0 { - if len(accumulation) == 0 { - return nil, fmt.Errorf("Unhandled drive listing output") - } - drives = append(drives, syscall.UTF16ToString(accumulation)) - accumulation = []uint16{} - } else { - accumulation = append(accumulation, v) - } - } - if len(accumulation) > 0 { - drives = append(drives, syscall.UTF16ToString(accumulation)) - } - - return drives, nil -} - -// GetURIRoot returns the root URI for the entire system -func (b *Simple) GetURIRoot() string { - return "" -} - -// ListDirectories returns a list of directories rooted at a particular path -func (b *Simple) ListDirectories(ctx context.Context, path string) ([]string, error) { - if path == "" { - return b.drives(ctx) - } - rets := []string{} - infos, err := ioutil.ReadDir(path) - if err != nil { - return rets, nil - } - for _, inf := range infos { - if inf.Mode().IsDir() { - if _, err := ioutil.ReadDir(filepath.Join(path, inf.Name())); err == nil { - rets = append(rets, inf.Name()) - } - } - } - return rets, nil -} - -// SupportsPerfetto returns true if the given device supports taking a -// Perfetto trace. -func (b *Simple) SupportsPerfetto(ctx context.Context) bool { - return false -} - -// ConnectPerfetto connects to a Perfetto service running on this device -// and returns an open socket connection to the service. -func (b *Simple) ConnectPerfetto(ctx context.Context) (*perfetto.Client, error) { - return nil, fmt.Errorf("Perfetto is not supported on this device") -} diff --git a/core/os/device/device.go b/core/os/device/device.go index 24ff3b6489..cf7f56571f 100644 --- a/core/os/device/device.go +++ b/core/os/device/device.go @@ -26,6 +26,7 @@ const ( X86_64 = Architecture_X86_64 MIPS = Architecture_MIPS MIPS64 = Architecture_MIPS64 + ARM64 = Architecture_ARM64 ) const ( @@ -40,13 +41,9 @@ const ( OSX = OSKind_OSX Linux = OSKind_Linux Android = OSKind_Android - Stadia = OSKind_Stadia + Fuchsia = OSKind_Fuchsia ) -func IsLinuxLike(k OSKind) bool { - return k == OSKind_Linux || k == OSKind_Stadia -} - var ( // ARMv7aLayout is the memory layout for the armv7a ABI. // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042f/IHI0042F_aapcs.pdf diff --git a/core/os/device/device.proto b/core/os/device/device.proto index ac6b7e11fb..f2b1881992 100644 --- a/core/os/device/device.proto +++ b/core/os/device/device.proto @@ -37,6 +37,7 @@ enum Architecture { X86_64 = 4; MIPS = 5; MIPS64 = 6; + ARM64 = 7; } // Endian represents a byte ordering specification for multi-yte values. @@ -53,7 +54,7 @@ enum OSKind { OSX = 2; Linux = 3; Android = 4; - Stadia = 5; + Fuchsia = 5; } // MemoryLayout holds information about how memory is fundamentally laid out for @@ -106,7 +107,7 @@ message ABI { // and feature set available. Architecture architecture = 3; // MemoryLayout specifies things like size and alignment of types used - // directly buy the ABI. + // directly by the ABI. MemoryLayout memory_layout = 4; } @@ -145,6 +146,8 @@ message GPU { string name = 1; // Vendor is the vendor of this GPU. string vendor = 2; + // Version is the version of the driver software of this GPU + uint32 version = 3; } // Hardware describes the physical configuration of a computing device. @@ -173,6 +176,8 @@ message Configuration { Drivers drivers = 4; // Perfetto capability. PerfettoCapability perfetto_capability = 5; + // ANGLE info. + ANGLE angle = 6; } message VulkanProfilingLayers { @@ -184,14 +189,26 @@ message PerfettoCapability { GPUProfiling gpu_profiling = 1; VulkanProfilingLayers vulkan_profile_layers = 2; bool can_specify_atrace_apps = 3; + bool has_frame_lifecycle = 4; + bool has_power_rail = 5; + bool can_download_while_tracing = 6; + // Whether the Perfetto tracing API supports tracing to a file on the device + // given a path, rather than a file descriptor. + bool can_provide_trace_file_path = 7; } message GPUProfiling { bool hasRenderStage = 1; // The GPU performance counters. GpuCounterDescriptor gpu_counter_descriptor = 2; - bool hasFrameLifecycle = 3; + reserved 3; bool has_render_stage_producer_layer = 4; + bool has_gpu_mem_total = 5; +} + +message ANGLE { + string package = 1; + int32 version = 2; } // Instance represents a physical device. @@ -210,32 +227,10 @@ message Instance { // Drivers describes the drivers available on a device. message Drivers { - // The OpenGL or OpenGL ES driver support. - OpenGLDriver opengl = 1; // The Vulkan driver support. VulkanDriver vulkan = 2; } -// OpenGLDriver describes the device driver support for the OpenGL or OpenGL ES -// APIs. -message OpenGLDriver { - // Supported extensions. e.g. "GL_KHR_debug", "GL_EXT_sRGB [...]". - repeated string extensions = 1; - // Driver name. e.g. "Adreno (TM) 320". - string renderer = 2; - // Driver vendor name. e.g. "Qualcomm". - string vendor = 3; - // Renderer version. e.g. "OpenGL ES 3.0 V@53.0 AU@ (CL@)". - string version = 4; - // Value returned by glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT) - uint32 uniform_buffer_alignment = 5; - // Value returned by glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS) - uint32 max_transform_feedback_separate_attribs = 6; - // Value returned by - // glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS) - uint32 max_transform_feedback_interleaved_components = 7; -} - // VulkanDriver describes the device driver support for the Vulkan API. message VulkanDriver { // Enumerated instance layers. @@ -245,6 +240,8 @@ message VulkanDriver { repeated string icd_and_implicit_layer_extensions = 2; // Physical devices that have Vulkan support. repeated VulkanPhysicalDevice physical_devices = 3; + // Driver package version + string version = 4; } // VulkanLayer describes the layers currently installed on the device, diff --git a/core/os/device/deviceinfo/cc/BUILD.bazel b/core/os/device/deviceinfo/cc/BUILD.bazel index 32edf4dccc..e7c3ac2885 100644 --- a/core/os/device/deviceinfo/cc/BUILD.bazel +++ b/core/os/device/deviceinfo/cc/BUILD.bazel @@ -17,10 +17,7 @@ load("//tools/build:rules.bzl", "android_dynamic_library", "cc_copts", "mm_libra mm_library( name = "darwin_query", srcs = glob(["osx/*.mm"]), - copts = cc_copts() + [ - # Avoid OpenGL deprecation error. - "-DGL_SILENCE_DEPRECATION", - ], + copts = cc_copts(), copy_hdrs = ["query.h"], deps = ["//core/os/device:device_cc_proto"], ) @@ -31,6 +28,8 @@ cc_library( "//tools/build:linux": glob(["linux/query.cpp"]), "//tools/build:windows": glob(["windows/query.cpp"]), "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], + "//tools/build:fuchsia-arm64": glob(["fuchsia/query.cpp"]), # Android "//conditions:default": glob([ "android/*.cpp", @@ -41,17 +40,13 @@ cc_library( copts = cc_copts(), linkopts = select({ "//tools/build:linux": [], - "//tools/build:darwin": [ - "-framework Cocoa", - "-framework OpenGL", - ], - "//tools/build:windows": ["-lgdi32"], + "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], + "//tools/build:fuchsia-arm64": [], + "//tools/build:windows": [], # Android "//conditions:default": [ - "-lEGL", - "-lGLESv2", "-llog", - "-landroid", "-lm", ], }), @@ -62,6 +57,11 @@ cc_library( "//core/os/device:device_cc_proto", ] + select({ "//tools/build:darwin": [":darwin_query"], + "//tools/build:darwin_arm64": [":darwin_query"], + "//tools/build:fuchsia-arm64": [ + "@fuchsia_sdk_dynamic//fidl/fuchsia.hwinfo:fuchsia.hwinfo_cc", + "@fuchsia_sdk_dynamic//pkg/sys_cpp", + ], "//conditions:default": [], }), ) diff --git a/core/os/device/deviceinfo/cc/android/egl_lite.h b/core/os/device/deviceinfo/cc/android/egl_lite.h deleted file mode 100644 index 45d91e103e..0000000000 --- a/core/os/device/deviceinfo/cc/android/egl_lite.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ - -#ifndef GAPID_CORE_OS_DEVICEINFO_ANDROID_EGL_LITE -#define GAPID_CORE_OS_DEVICEINFO_ANDROID_EGL_LITE - -typedef int EGLBoolean; -typedef int EGLint; -typedef unsigned int EGLenum; -typedef void* EGLConfig; -typedef void* EGLContext; -typedef void* EGLDisplay; -typedef void* EGLNativeDisplayType; -typedef void* EGLSurface; - -const EGLint EGL_PBUFFER_BIT = 0x0001; -const EGLint EGL_OPENGL_ES2_BIT = 0x0004; - -const EGLint EGL_TRUE = 0x0001; -const EGLint EGL_SUCCESS = 0x3000; -const EGLint EGL_BUFFER_SIZE = 0x3020; -const EGLint EGL_ALPHA_SIZE = 0x3021; -const EGLint EGL_BLUE_SIZE = 0x3022; -const EGLint EGL_GREEN_SIZE = 0x3023; -const EGLint EGL_RED_SIZE = 0x3024; -const EGLint EGL_DEPTH_SIZE = 0x3025; -const EGLint EGL_STENCIL_SIZE = 0x3026; -const EGLint EGL_CONFIG_ID = 0x3028; -const EGLint EGL_SURFACE_TYPE = 0x3033; -const EGLint EGL_NONE = 0x3038; -const EGLint EGL_RENDERABLE_TYPE = 0x3040; -const EGLint EGL_EXTENSIONS = 0x3055; -const EGLint EGL_HEIGHT = 0x3056; -const EGLint EGL_WIDTH = 0x3057; -const EGLint EGL_SWAP_BEHAVIOR = 0x3093; -const EGLint EGL_BUFFER_PRESERVED = 0x3094; -const EGLint EGL_CONTEXT_CLIENT_VERSION = 0x3098; -const EGLint EGL_OPENGL_ES_API = 0x30A0; - -const EGLNativeDisplayType EGL_DEFAULT_DISPLAY = nullptr; -const EGLContext EGL_NO_CONTEXT = 0; -const EGLDisplay EGL_NO_DISPLAY = 0; -const EGLSurface EGL_NO_SURFACE = 0; - -typedef EGLBoolean (*PFNEGLBINDAPI)(EGLenum api); -typedef EGLBoolean (*PFNEGLCHOOSECONFIG)(EGLDisplay display, - const EGLint* attrib_list, - EGLConfig* configs, EGLint config_size, - EGLint* num_config); -typedef EGLBoolean (*PFNEGLDESTROYCONTEXT)(EGLDisplay display, - EGLContext context); -typedef EGLBoolean (*PFNEGLDESTROYSURFACE)(EGLDisplay display, - EGLSurface surface); -typedef EGLBoolean (*PFNEGLINITIALIZE)(EGLDisplay dpy, EGLint* major, - EGLint* minor); -typedef EGLBoolean (*PFNEGLMAKECURRENT)(EGLDisplay display, EGLSurface draw, - EGLSurface read, EGLContext context); -typedef EGLBoolean (*PFNEGLTERMINATE)(EGLDisplay display); -typedef EGLContext (*PFNEGLCREATECONTEXT)(EGLDisplay display, EGLConfig config, - EGLContext share_context, - const EGLint* attrib_list); -typedef EGLDisplay (*PFNEGLGETDISPLAY)(EGLNativeDisplayType native_display); -typedef EGLint (*PFNEGLGETERROR)(); -typedef EGLSurface (*PFNEGLCREATEPBUFFERSURFACE)(EGLDisplay display, - EGLConfig config, - const EGLint* attrib_list); -typedef const char* (*PFNEGLQUERYSTRING)(EGLDisplay display, EGLint name); -typedef EGLBoolean (*PFNEGLRELEASETHREAD)(); - -#endif // GAPID_CORE_OS_DEVICEINFO_ANDROID_EGL_LITE diff --git a/core/os/device/deviceinfo/cc/android/query.cpp b/core/os/device/deviceinfo/cc/android/query.cpp index 2d694816e7..8ada474e58 100644 --- a/core/os/device/deviceinfo/cc/android/query.cpp +++ b/core/os/device/deviceinfo/cc/android/query.cpp @@ -14,12 +14,9 @@ * limitations under the License. */ -#include "egl_lite.h" - #include "../query.h" #include "core/cc/assert.h" -#include "core/cc/get_gles_proc_address.h" #include "core/cc/log.h" #include @@ -28,15 +25,9 @@ #include #include -#define LOG_ERR(...) \ - __android_log_print(ANDROID_LOG_ERROR, "GAPID", __VA_ARGS__); - -#define LOG_WARN(...) \ - __android_log_print(ANDROID_LOG_WARN, "GAPID", __VA_ARGS__); +#define LOG_ERR(...) __android_log_print(ANDROID_LOG_ERROR, "AGI", __VA_ARGS__); -typedef int GLint; -typedef unsigned int GLuint; -typedef uint8_t GLubyte; +#define LOG_WARN(...) __android_log_print(ANDROID_LOG_WARN, "AGI", __VA_ARGS__); namespace { @@ -125,175 +116,36 @@ void abiByName(const std::string name, device::ABI* abi) { } } -} // anonymous namespace - -namespace query { - -struct Context { - char mError[512]; - EGLDisplay mDisplay; - EGLSurface mSurface; - EGLContext mContext; - int mNumCores; - std::string mHost; - std::string mSerial; - std::string mHardware; - std::string mOSName; - std::string mOSBuild; - int mOSVersion; - int mOSVersionMajor; - int mOSVersionMinor; - std::vector mSupportedABIs; - device::Architecture mCpuArchitecture; - std::string eglExtensions; -}; - -static Context gContext; -static int gContextRefCount = 0; - -void destroyContext() { - if (--gContextRefCount > 0) { - return; - } - - auto eglMakeCurrent = reinterpret_cast( - core::GetGlesProcAddress("eglMakeCurrent")); - auto eglDestroyContext = reinterpret_cast( - core::GetGlesProcAddress("eglDestroyContext")); - auto eglDestroySurface = reinterpret_cast( - core::GetGlesProcAddress("eglDestroySurface")); - auto eglTerminate = reinterpret_cast( - core::GetGlesProcAddress("eglTerminate")); - auto eglReleaseThread = reinterpret_cast( - core::GetGlesProcAddress("eglReleaseThread")); - - if (gContext.mContext) { - eglMakeCurrent(gContext.mDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, - EGL_NO_CONTEXT); - eglDestroyContext(gContext.mDisplay, gContext.mContext); - gContext.mContext = 0; - } - if (gContext.mSurface) { - eglDestroySurface(gContext.mDisplay, gContext.mSurface); - gContext.mSurface = 0; - } - if (gContext.mDisplay) { - eglReleaseThread(); - eglTerminate(gContext.mDisplay); - gContext.mDisplay = nullptr; - } -} - -bool createContext() { - if (gContextRefCount++ > 0) { - return true; - } - - gContext.mDisplay = nullptr; - gContext.mSurface = nullptr; - gContext.mContext = nullptr; - gContext.mNumCores = 0; - -#define RESOLVE(name, pfun) \ - auto name = reinterpret_cast(core::GetGlesProcAddress(#name)); \ - GAPID_ASSERT(name != nullptr) - - RESOLVE(eglGetError, PFNEGLGETERROR); - RESOLVE(eglInitialize, PFNEGLINITIALIZE); - RESOLVE(eglBindAPI, PFNEGLBINDAPI); - RESOLVE(eglChooseConfig, PFNEGLCHOOSECONFIG); - RESOLVE(eglCreateContext, PFNEGLCREATECONTEXT); - RESOLVE(eglCreatePbufferSurface, PFNEGLCREATEPBUFFERSURFACE); - RESOLVE(eglMakeCurrent, PFNEGLMAKECURRENT); - RESOLVE(eglGetDisplay, PFNEGLGETDISPLAY); - RESOLVE(eglQueryString, PFNEGLQUERYSTRING); - -#undef RESOLVE - -#define CHECK(x) \ - x; \ - { \ - EGLint error = eglGetError(); \ - if (error != EGL_SUCCESS) { \ - snprintf(gContext.mError, sizeof(gContext.mError), \ - "EGL error: 0x%x when executing:\n " #x, error); \ - destroyContext(); \ - return false; \ - } \ - } - - CHECK(auto display = eglGetDisplay(EGL_DEFAULT_DISPLAY)); - - EGLint major, minor; - CHECK(eglInitialize(display, &major, &minor)); - - gContext.mDisplay = display; - - if (major > 1 || minor >= 5) { - // Client extensions (null display) were added in EGL 1.5. - CHECK(auto exts = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS)); - gContext.eglExtensions.append(exts); - gContext.eglExtensions.append(" "); - } - CHECK(auto exts = eglQueryString(display, EGL_EXTENSIONS)); - gContext.eglExtensions.append(exts); - - CHECK(eglBindAPI(EGL_OPENGL_ES_API)); - - // Find a supported EGL context config. - int r = 8, g = 8, b = 8, a = 8, d = 24, s = 8; - const int configAttribList[] = { - // clang-format off - EGL_RED_SIZE, r, - EGL_GREEN_SIZE, g, - EGL_BLUE_SIZE, b, - EGL_ALPHA_SIZE, a, - EGL_BUFFER_SIZE, r+g+b+a, - EGL_DEPTH_SIZE, d, - EGL_STENCIL_SIZE, s, - EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_NONE - // clang-format on - }; - - int one = 1; - EGLConfig eglConfig; - - CHECK(eglChooseConfig(display, configAttribList, &eglConfig, 1, &one)); - - // Create an EGL context. - const int contextAttribList[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; +typedef struct { + int major, minor; +} AndroidVersion; - CHECK(gContext.mContext = eglCreateContext(display, eglConfig, EGL_NO_CONTEXT, - contextAttribList)); +const AndroidVersion kVersionBySdk[] = { + {0, 0}, {1, 0}, {1, 1}, {1, 5}, {1, 6}, /* 0- 4 */ + {2, 0}, {2, 0}, {2, 1}, {2, 2}, {2, 3}, /* 5- 9 */ + {2, 3}, {3, 0}, {3, 1}, {3, 2}, {4, 0}, /* 10-14 */ + {4, 0}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, /* 15-19 */ + {4, 4}, {5, 0}, {5, 1}, {6, 0}, {7, 0}, /* 20-24 */ + {7, 1}, {8, 0}, {8, 1}, {9, 0}, {10, 0}, /* 25-29*/ + {11, 0}}; +constexpr int kMaxKnownSdk = (sizeof(kVersionBySdk) / sizeof(AndroidVersion)); - const int surfaceAttribList[] = { - // clang-format off - EGL_WIDTH, 16, - EGL_HEIGHT, 16, - EGL_NONE - // clang-format on - }; - - CHECK(gContext.mSurface = - eglCreatePbufferSurface(display, eglConfig, surfaceAttribList)); - - CHECK(eglMakeCurrent(display, gContext.mSurface, gContext.mSurface, - gContext.mContext)); +} // anonymous namespace -#undef CHECK +namespace query { -#define GET_PROP(name, trans) \ - do { \ - char _v[PROP_VALUE_MAX] = {0}; \ - if (__system_property_get(name, _v) == 0) { \ - snprintf(gContext.mError, sizeof(gContext.mError), \ - "Failed reading property %s", name); \ - destroyContext(); \ - return false; \ - } \ - trans; \ +bool queryPlatform(PlatformInfo* info, std::string* errorMsg) { + info->numCpuCores = 0; + +#define GET_PROP(name, trans) \ + do { \ + char _v[PROP_VALUE_MAX] = {0}; \ + if (__system_property_get(name, _v) == 0) { \ + errorMsg->append("Failed reading property "); \ + errorMsg->append(name); \ + return false; \ + } \ + trans; \ } while (0) #define GET_STRING_PROP(n, t) GET_PROP(n, t = _v) @@ -310,154 +162,47 @@ bool createContext() { std::string manufacturer; std::string model; - - GET_STRING_LIST_PROP("ro.product.cpu.abilist", gContext.mSupportedABIs); - GET_STRING_PROP("ro.build.host", gContext.mHost); GET_STRING_PROP("ro.product.manufacturer", manufacturer); GET_STRING_PROP("ro.product.model", model); - GET_STRING_PROP("ro.hardware", gContext.mHardware); - GET_STRING_PROP("ro.build.display.id", gContext.mOSBuild); - + GET_STRING_PROP("ro.hardware", info->hardwareName); if (model != "") { if (manufacturer != "") { - gContext.mHardware = manufacturer + " " + model; + info->name = manufacturer + " " + model; } else { - gContext.mHardware = model; + info->name = model; } + } else { + info->name = info->hardwareName; } - GET_STRING_PROP("ro.build.version.release", gContext.mOSName); - GET_INT_PROP("ro.build.version.sdk", gContext.mOSVersion); - - if (gContext.mSupportedABIs.size() > 0) { - auto primaryABI = gContext.mSupportedABIs[0]; - if (primaryABI == "armeabi-v7a") { - gContext.mCpuArchitecture = device::ARMv7a; - } else if (primaryABI == "arm64-v8a") { - gContext.mCpuArchitecture = device::ARMv8a; - } else if (primaryABI == "x86") { - gContext.mCpuArchitecture = device::X86; - } else if (primaryABI == "x86_64") { - gContext.mCpuArchitecture = device::X86_64; - } else { - LOG_WARN("Unrecognised ABI: %s", primaryABI.c_str()); - } + std::vector supportedABIs; + GET_STRING_LIST_PROP("ro.product.cpu.abilist", supportedABIs); + info->abis.resize(supportedABIs.size()); + for (size_t i = 0; i < supportedABIs.size(); i++) { + abiByName(supportedABIs[i], &info->abis[i]); } - switch (gContext.mOSVersion) { - case 29: // Android 10 - gContext.mOSVersionMajor = 10; - gContext.mOSVersionMinor = 0; - case 28: // Pie - gContext.mOSVersionMajor = 9; - gContext.mOSVersionMinor = 0; - break; - case 27: // Oreo - gContext.mOSVersionMajor = 8; - gContext.mOSVersionMinor = 1; - break; - case 26: // Oreo - gContext.mOSVersionMajor = 8; - gContext.mOSVersionMinor = 0; - break; - case 25: // Nougat - gContext.mOSVersionMajor = 7; - gContext.mOSVersionMinor = 1; - break; - case 24: // Nougat - gContext.mOSVersionMajor = 7; - gContext.mOSVersionMinor = 0; - break; - case 23: // Marshmallow - gContext.mOSVersionMajor = 6; - gContext.mOSVersionMinor = 0; - break; - case 22: // Lollipop - gContext.mOSVersionMajor = 5; - gContext.mOSVersionMinor = 1; - break; - case 21: // Lollipop - gContext.mOSVersionMajor = 5; - gContext.mOSVersionMinor = 0; - break; - case 19: // KitKat - gContext.mOSVersionMajor = 4; - gContext.mOSVersionMinor = 4; - break; - case 18: // Jelly Bean - gContext.mOSVersionMajor = 4; - gContext.mOSVersionMinor = 3; - break; - case 17: // Jelly Bean - gContext.mOSVersionMajor = 4; - gContext.mOSVersionMinor = 2; - break; - case 16: // Jelly Bean - gContext.mOSVersionMajor = 4; - gContext.mOSVersionMinor = 1; - break; - case 15: // Ice Cream Sandwich - case 14: // Ice Cream Sandwich - gContext.mOSVersionMajor = 4; - gContext.mOSVersionMinor = 0; - break; - case 13: // Honeycomb - gContext.mOSVersionMajor = 3; - gContext.mOSVersionMinor = 2; - break; - case 12: // Honeycomb - gContext.mOSVersionMajor = 3; - gContext.mOSVersionMinor = 1; - break; - case 11: // Honeycomb - gContext.mOSVersionMajor = 3; - gContext.mOSVersionMinor = 0; - break; - case 10: // Gingerbread - case 9: // Gingerbread - gContext.mOSVersionMajor = 2; - gContext.mOSVersionMinor = 3; - break; - case 8: // Froyo - gContext.mOSVersionMajor = 2; - gContext.mOSVersionMinor = 2; - break; - case 7: // Eclair - gContext.mOSVersionMajor = 2; - gContext.mOSVersionMinor = 1; - break; - case 6: // Eclair - case 5: // Eclair - gContext.mOSVersionMajor = 2; - gContext.mOSVersionMinor = 0; - break; - case 4: // Donut - gContext.mOSVersionMajor = 1; - gContext.mOSVersionMinor = 6; - break; - case 3: // Cupcake - gContext.mOSVersionMajor = 1; - gContext.mOSVersionMinor = 5; - break; - case 2: // (no code name) - gContext.mOSVersionMajor = 1; - gContext.mOSVersionMinor = 1; - break; - case 1: // (no code name) - gContext.mOSVersionMajor = 1; - gContext.mOSVersionMinor = 0; - break; + info->numCpuCores = 0; + + info->osKind = device::Android; + GET_STRING_PROP("ro.build.version.release", info->osName); + GET_STRING_PROP("ro.build.display.id", info->osBuild); + int sdkVersion = 0; + GET_INT_PROP("ro.build.version.sdk", sdkVersion); + // preview_sdk is used to determine the version for the next OS release + // Until the official release, the new OS releases will use the same sdk + // version as the previous OS while setting the preview_sdk + int previewSdk = 0; + GET_INT_PROP("ro.build.version.preview_sdk", previewSdk); + sdkVersion += previewSdk; + if (sdkVersion >= 0 && sdkVersion < kMaxKnownSdk) { + info->osMajor = kVersionBySdk[sdkVersion].major; + info->osMinor = kVersionBySdk[sdkVersion].minor; } return true; } -const char* contextError() { return gContext.mError; } - -bool hasGLorGLES() { return true; } - -int numABIs() { return gContext.mSupportedABIs.size(); } - device::ABI* currentABI() { device::ABI* out = new device::ABI(); #if defined(__arm__) @@ -474,52 +219,10 @@ device::ABI* currentABI() { return out; } -void abi(int idx, device::ABI* abi) { - return abiByName(gContext.mSupportedABIs[idx], abi); -} - -int cpuNumCores() { return gContext.mNumCores; } - -const char* cpuName() { return ""; } - -const char* cpuVendor() { return ""; } - -device::Architecture cpuArchitecture() { return gContext.mCpuArchitecture; } - -const char* gpuName() { return ""; } - -const char* gpuVendor() { return ""; } - -const char* instanceName() { return gContext.mSerial.c_str(); } - -const char* hardwareName() { return gContext.mHardware.c_str(); } - -device::OSKind osKind() { return device::Android; } - -const char* osName() { return gContext.mOSName.c_str(); } - -const char* osBuild() { return gContext.mOSBuild.c_str(); } - -int osMajor() { return gContext.mOSVersionMajor; } - -int osMinor() { return gContext.mOSVersionMinor; } - -int osPoint() { return 0; } - -void glDriverPlatform(device::OpenGLDriver* driver) { - std::istringstream iss(gContext.eglExtensions); - std::string extension; - while (std::getline(iss, extension, ' ')) { - if (extension != "") { - driver->add_extensions(extension); - } - } -} - device::VulkanProfilingLayers* get_vulkan_profiling_layers() { auto layers = new device::VulkanProfilingLayers(); layers->set_cpu_timing(true); - layers->set_memory_tracker(true); + layers->set_memory_tracker(false); return layers; } diff --git a/core/os/device/deviceinfo/cc/cpu.cpp b/core/os/device/deviceinfo/cc/cpu.cpp index fc163948d6..328395105b 100644 --- a/core/os/device/deviceinfo/cc/cpu.cpp +++ b/core/os/device/deviceinfo/cc/cpu.cpp @@ -18,13 +18,10 @@ #include "core/cc/target.h" -#if (defined(__x86_64) || defined(__i386)) && (TARGET_OS != GAPID_OS_ANDROID) -#if !defined(_MSC_VER) || defined(__GNUC__) +#if (defined(__x86_64) || defined(__i386)) #include -namespace query { - -const char* cpuName() { +bool query::queryCpu(CpuInfo* info, std::string* error) { static union { uint32_t reg[12]; char str[49]; @@ -32,41 +29,156 @@ const char* cpuName() { if (__get_cpuid(0x80000002, ®[0], ®[1], ®[2], ®[3]) && __get_cpuid(0x80000003, ®[4], ®[5], ®[6], ®[7]) && __get_cpuid(0x80000004, ®[8], ®[9], ®[10], ®[11])) { - return str; + info->name = str; + } else { + error->append("Failed to query CPUID"); + return false; } - return ""; -} -const char* cpuVendor() { - static union { - uint32_t reg[3]; - char str[13]; - }; + str[12] = 0; // In case the below 12 byte vendor name uses exactly 12 chars. uint32_t eax = 0; if (__get_cpuid(0, &eax, ®[0], ®[2], ®[1])) { - return str; + info->vendor = str; + } else { + error->append("Failed to query CPUID"); + return false; + } + + info->architecture = device::X86_64; + return true; +} + +#elif (defined(__arm64)) +#include + +// Melih TODO: Implement this properly +bool query::queryCpu(CpuInfo* info, std::string* error) { + // The only Arm64 device is not Apple M1 + // Therefore add this temporary solution + + info->name = "darwin_arm64"; + info->architecture = device::ARM64; + info->vendor = "ARM"; + return true; +} + +#elif ((defined(__arm__) || defined(__aarch64__)) && \ + TARGET_OS == GAPID_OS_ANDROID) +#include +#include + +bool query::queryCpu(CpuInfo* info, std::string* error) { + std::fstream proc("/proc/cpuinfo", std::ios_base::in); + if (proc.is_open()) { + std::string line, processor, hardware; + while (std::getline(proc, line)) { + size_t colon = line.rfind(": "); + if (colon == std::string::npos) { + } else if (line.rfind("Hardware") == 0) { + hardware = line.substr(colon + 2); + } else if (line.rfind("Processor") == 0) { + processor = line.substr(colon + 2); + } + } + proc.close(); + + if (hardware != "") { + info->name = hardware; + } else if (processor != "") { + info->name = processor; + } + } + + if (info->name == "") { + static const char* cpuProps[] = { + "ro.boot.hardware.platform", + "ro.hardware.chipname", + "ro.boot.hardware", + "ro.hardware", + "ro.arch", + }; + char str[PROP_VALUE_MAX]; + for (const char* prop : cpuProps) { + if (__system_property_get(prop, str) != 0) { + info->name = str; + break; + } + } } - return ""; + + info->vendor = "ARM"; // TODO: get the implementer? +#ifdef __arm__ + info->architecture = device::ARMv7a; +#else + info->architecture = device::ARMv8a; +#endif + return true; } -device::Architecture cpuArchitecture() { return device::X86_64; } +#elif ((defined(__arm__) || defined(__aarch64__)) && \ + TARGET_OS == GAPID_OS_FUCHSIA) +#include +#include -} // namespace query -#else // !defined(_MSC_VER) || defined(__GNUC__) -// If we are using MSVC (rather than MSYS) we cannot use __get_cpuid -namespace query { +bool query::queryCpu(CpuInfo* info, std::string* error) { + auto context = sys::ComponentContext::Create(); -const char* cpuName() { return ""; } + // Name, Architecture + fuchsia::hwinfo::BoardSyncPtr board_ptr; + if (context->svc()->Connect(board_ptr.NewRequest()) != ZX_OK) { + error->append("Failed board context Connect() call."); + return false; + } -const char* cpuVendor() { return ""; } + fuchsia::hwinfo::BoardInfo board_info; + if (board_ptr->GetInfo(&board_info) != ZX_OK) { + error->append("Failed fuchsia board GetInfo() call."); + return false; + } -device::Architecture cpuArchitecture() { -#ifdef _WIN64 - return device::X86_64; -#elif defined _WIN32 - return device::X86; + if (!board_info.has_cpu_architecture()) { + error->append("Unspecified board cpu architecture."); + return false; + } + switch (board_info.cpu_architecture()) { + case fuchsia::hwinfo::Architecture::ARM64: +#ifdef __arm__ + info->architecture = device::ARMv7a; +#else + info->architecture = device::ARMv8a; #endif + break; + default: + error->append("Unknown ARM cpu architecture."); + return false; + } + + if (!board_info.has_name()) { + error->append("Unspecified board name."); + return false; + } + info->name = board_info.name(); + + // Vendor + fuchsia::hwinfo::ProductSyncPtr product_ptr; + if (context->svc()->Connect(product_ptr.NewRequest()) != ZX_OK) { + error->append("Failed product context Connect() call."); + return false; + } + fuchsia::hwinfo::ProductInfo product_info; + if (product_ptr->GetInfo(&product_info) != ZX_OK) { + error->append("Failed fuchsia product GetInfo() call."); + return false; + } + if (!product_info.has_manufacturer()) { + error->append("Unspecified product manufacturer."); + return false; + } + info->vendor = product_info.manufacturer(); + + return true; } -} // namespace query + +#else +#error Unsupported target architecture. #endif -#endif \ No newline at end of file diff --git a/core/os/device/deviceinfo/cc/fuchsia/query.cpp b/core/os/device/deviceinfo/cc/fuchsia/query.cpp new file mode 100644 index 0000000000..8cdc9294a2 --- /dev/null +++ b/core/os/device/deviceinfo/cc/fuchsia/query.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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 "core/os/device/deviceinfo/cc/query.h" + +#include +#include + +#include "core/cc/dl_loader.h" + +#define STR_OR_EMPTY(x) ((x != nullptr) ? x : "") + +namespace { + +device::ABI* abi(device::ABI* abi) { + // TODO: need aarch64 abi differentiation ? + abi->set_name("ARMv8a"); + abi->set_os(device::Fuchsia); + abi->set_architecture(device::ARMv8a); + abi->set_allocated_memory_layout(query::currentMemoryLayout()); + return abi; +} + +} // namespace + +namespace query { + +bool queryPlatform(PlatformInfo* info, std::string* errorMsg) { + char hostname[HOST_NAME_MAX + 1]; + if (gethostname(hostname, sizeof(hostname)) != 0) { + errorMsg->append("gethostname returned error: " + std::to_string(errno)); + return false; + } + info->name = hostname; + info->abis.resize(1); + abi(&info->abis[0]); + + utsname ubuf; + if (uname(&ubuf) != 0) { + errorMsg->append("uname returned error: " + std::to_string(errno)); + return false; + } + info->hardwareName = STR_OR_EMPTY(ubuf.machine); + + info->numCpuCores = sysconf(_SC_NPROCESSORS_CONF); + + info->osKind = device::Fuchsia; + info->osName = STR_OR_EMPTY(ubuf.release); + info->osBuild = STR_OR_EMPTY(ubuf.version); + + return true; +} + +device::ABI* currentABI() { return abi(new device::ABI()); } + +device::VulkanProfilingLayers* get_vulkan_profiling_layers() { + auto layers = new device::VulkanProfilingLayers(); + layers->set_cpu_timing(true); + layers->set_memory_tracker(true); + return layers; +} + +bool hasAtrace() { return false; } + +} // namespace query diff --git a/core/os/device/deviceinfo/cc/gl.cpp b/core/os/device/deviceinfo/cc/gl.cpp deleted file mode 100644 index a9e67619e5..0000000000 --- a/core/os/device/deviceinfo/cc/gl.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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 "gl_lite.h" -#include "query.h" - -#include "core/cc/assert.h" -#include "core/cc/get_gles_proc_address.h" -#include "core/cc/log.h" - -#include - -namespace query { - -const char* safe_string(void* x) { - return (x != nullptr) ? reinterpret_cast(x) : ""; -} - -void glDriver(device::OpenGLDriver* driver) { - GLint major_version = 2; - GLint minor_version = 0; - GLint uniformbufferalignment = 1; - GLint maxtransformfeedbackseparateattribs = 0; - GLint maxtransformfeedbackinterleavedcomponents = 0; - - auto glGetIntegerv = reinterpret_cast( - core::GetGlesProcAddress("glGetIntegerv")); - auto glGetError = - reinterpret_cast(core::GetGlesProcAddress("glGetError")); - auto glGetString = - reinterpret_cast(core::GetGlesProcAddress("glGetString")); - auto glGetStringi = reinterpret_cast( - core::GetGlesProcAddress("glGetStringi")); - - GAPID_ASSERT(glGetError != nullptr); - GAPID_ASSERT(glGetString != nullptr); - - glGetError(); // Clear error state. - glGetIntegerv(GL_MAJOR_VERSION, &major_version); - glGetIntegerv(GL_MINOR_VERSION, &minor_version); - if (glGetError() != GL_NO_ERROR) { - // GL_MAJOR_VERSION/GL_MINOR_VERSION were introduced in GLES 3.0, - // so if the commands returned error we assume it is GLES 2.0. - major_version = 2; - minor_version = 0; - } - - if (major_version >= 3) { - GAPID_ASSERT(glGetIntegerv != nullptr); - GAPID_ASSERT(glGetStringi != nullptr); - - glGetIntegerv(GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT, &uniformbufferalignment); - glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, - &maxtransformfeedbackseparateattribs); - glGetIntegerv(GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS, - &maxtransformfeedbackinterleavedcomponents); - - int32_t c = 0; - glGetIntegerv(GL_NUM_EXTENSIONS, &c); - for (int32_t i = 0; i < c; i++) { - driver->add_extensions(safe_string(glGetStringi(GL_EXTENSIONS, i))); - } - } else { - std::string extensions = safe_string(glGetString(GL_EXTENSIONS)); - if (glGetError() == GL_NO_ERROR) { - std::istringstream iss(extensions); - std::string extension; - while (std::getline(iss, extension, ' ')) { - driver->add_extensions(extension); - } - } - } - - driver->set_renderer(safe_string(glGetString(GL_RENDERER))); - driver->set_vendor(safe_string(glGetString(GL_VENDOR))); - driver->set_version(safe_string(glGetString(GL_VERSION))); - driver->set_uniform_buffer_alignment(uniformbufferalignment); - driver->set_max_transform_feedback_separate_attribs( - maxtransformfeedbackseparateattribs); - driver->set_max_transform_feedback_interleaved_components( - maxtransformfeedbackinterleavedcomponents); - - glDriverPlatform(driver); -} - -} // namespace query diff --git a/core/os/device/deviceinfo/cc/gl_lite.h b/core/os/device/deviceinfo/cc/gl_lite.h deleted file mode 100644 index 5f14eac1ba..0000000000 --- a/core/os/device/deviceinfo/cc/gl_lite.h +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ - -#ifndef GAPID_CORE_OS_DEVICEINFO_GL_LITE -#define GAPID_CORE_OS_DEVICEINFO_GL_LITE - -typedef unsigned int GLenum; -typedef unsigned char GLubyte; -typedef unsigned int GLuint; -typedef int GLint; - -const GLint GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT = 0x8A34; - -const GLint GL_NO_ERROR = 0x0000; -const GLint GL_VENDOR = 0x1F00; -const GLint GL_RENDERER = 0x1F01; -const GLint GL_VERSION = 0x1F02; -const GLint GL_EXTENSIONS = 0x1F03; -const GLint GL_MINOR_VERSION = 0x821C; -const GLint GL_MAJOR_VERSION = 0x821B; -const GLint GL_NUM_EXTENSIONS = 0x821D; -const GLint GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS = 0x8C8A; -const GLint GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS = 0x8C8B; - -typedef void (*PFNGLGETINTEGERV)(GLenum param, GLint* values); -typedef GLenum (*PFNGLGETERROR)(); -typedef GLubyte* (*PFNGLGETSTRING)(GLenum param); -typedef GLubyte* (*PFNGLGETSTRINGI)(GLenum name, GLuint index); - -#endif // GAPID_CORE_OS_DEVICEINFO_GL_LITE diff --git a/core/os/device/deviceinfo/cc/instance.cpp b/core/os/device/deviceinfo/cc/instance.cpp index d7aeb03996..4d60832692 100644 --- a/core/os/device/deviceinfo/cc/instance.cpp +++ b/core/os/device/deviceinfo/cc/instance.cpp @@ -15,8 +15,15 @@ */ #include "instance.h" +#include "core/cc/log.h" #include "query.h" +namespace { + +std::string error; + +} // anonymous namespace + extern "C" { device_instance get_device_instance() { @@ -25,13 +32,15 @@ device_instance get_device_instance() { query::Option query_opt; query_opt.vulkan.set_query_layers_and_extensions(true) .set_query_physical_devices(true); - auto instance = query::getDeviceInstance(query_opt); + error.clear(); + auto instance = query::getDeviceInstance(query_opt, &error); if (!instance) { + GAPID_ERROR("Failed to query device info: %s", error.c_str()); return out; } // Reserialize the instance with the ID field. - out.size = instance->ByteSize(); + out.size = instance->ByteSizeLong(); out.data = new uint8_t[out.size]; instance->SerializeToArray(out.data, out.size); @@ -40,7 +49,7 @@ device_instance get_device_instance() { return out; } -const char* get_device_instance_error() { return query::contextError(); } +const char* get_device_instance_error() { return error.c_str(); } void free_device_instance(device_instance di) { delete[] di.data; } diff --git a/core/os/device/deviceinfo/cc/linux/query.cpp b/core/os/device/deviceinfo/cc/linux/query.cpp index dd6c67f5e1..0423fc89a0 100644 --- a/core/os/device/deviceinfo/cc/linux/query.cpp +++ b/core/os/device/deviceinfo/cc/linux/query.cpp @@ -17,10 +17,7 @@ #include "../query.h" #include "core/cc/dl_loader.h" -#include "core/cc/get_gles_proc_address.h" -#include "core/cc/gl/versions.h" -#include #include #include #include @@ -36,265 +33,47 @@ #define STR_OR_EMPTY(x) ((x != nullptr) ? x : "") -namespace query { - -struct Context { - char mError[512]; - Display* mDisplay; - GLXFBConfig* mFBConfigs; - GLXContext mGlCtx; - GLXPbuffer mPbuffer; - int mNumCores; - utsname mUbuf; - char mHostName[512]; -}; - -static Context gContext; -static int gContextRefCount = 0; - -typedef GLXFBConfig* (*pfn_glXChooseFBConfig)(Display* dpy, int screen, - const int* attrib_list, - int* nelements); -typedef GLXContext (*pfn_glXCreateNewContext)(Display* dpy, GLXFBConfig config, - int render_type, - GLXContext shader_list, - bool direct); -typedef GLXPbuffer (*pfn_glXCreatePbuffer)(Display* dpy, GLXFBConfig config, - const int* attrib_list); -typedef void (*pfn_glXDestroyPbuffer)(Display* dpy, GLXPbuffer pbuf); -typedef void (*pfn_glXDestroyContext)(Display* dpy, GLXContext ctx); -typedef Bool (*pfn_glXMakeContextCurrent)(Display* dpy, GLXDrawable draw, - GLXDrawable read, GLXContext ctx); -typedef GLXContext (*pfn_glXCreateContextAttribsARB)(Display* dpy, - GLXFBConfig config, - GLXContext share_context, - Bool direct, - const int* attrib_list); - -typedef int (*pfn_XFree)(void*); -typedef int (*pfn_XCloseDisplay)(Display*); -typedef Display* (*pfn_XOpenDisplay)(_Xconst char*); -typedef XErrorHandler (*pfn_XSetErrorHandler)(XErrorHandler); - -void destroyContext() { - if (--gContextRefCount > 0) { - return; - } - - if (!core::DlLoader::can_load("libX11.so")) { - return; - } - - if (!core::hasGLorGLES()) { - return; - } - - core::DlLoader libX("libX11.so"); - pfn_XFree fn_XFree = (pfn_XFree)libX.lookup("XFree"); - pfn_XCloseDisplay fn_XCloseDisplay = - (pfn_XCloseDisplay)libX.lookup("XCloseDisplay"); - - pfn_glXDestroyPbuffer fn_glXDestroyPbuffer = - (pfn_glXDestroyPbuffer)core::GetGlesProcAddress("glXDestroyPbuffer"); - pfn_glXDestroyContext fn_glXDestroyContext = - (pfn_glXDestroyContext)core::GetGlesProcAddress("glXDestroyContext"); - - if (gContext.mPbuffer && fn_glXDestroyPbuffer) { - (*fn_glXDestroyPbuffer)(gContext.mDisplay, gContext.mPbuffer); - gContext.mPbuffer = 0; - } - if (gContext.mGlCtx && fn_glXDestroyContext) { - (*fn_glXDestroyContext)(gContext.mDisplay, gContext.mGlCtx); - gContext.mGlCtx = nullptr; - } - if (gContext.mFBConfigs) { - fn_XFree(gContext.mFBConfigs); - gContext.mFBConfigs = nullptr; - } - if (gContext.mDisplay) { - fn_XCloseDisplay(gContext.mDisplay); - gContext.mDisplay = nullptr; - } -} - -void createGlContext() { - if (!core::hasGLorGLES()) { - return; - } - auto fn_glXChooseFBConfig = - (pfn_glXChooseFBConfig)core::GetGlesProcAddress("glXChooseFBConfig"); - auto fn_glXCreateNewContext = - (pfn_glXCreateNewContext)core::GetGlesProcAddress("glXCreateNewContext"); - auto fn_glXCreatePbuffer = - (pfn_glXCreatePbuffer)core::GetGlesProcAddress("glXCreatePbuffer"); - auto fn_glXMakeContextCurrent = - (pfn_glXMakeContextCurrent)core::GetGlesProcAddress( - "glXMakeContextCurrent"); - auto fn_glXCreateContextAttribsARB = - (pfn_glXCreateContextAttribsARB)core::GetGlesProcAddress( - "glXCreateContextAttribsARB"); - - if (!fn_glXChooseFBConfig || !fn_glXCreateNewContext || - !fn_glXCreatePbuffer || !fn_glXMakeContextCurrent) { - return; - } - - if (!core::DlLoader::can_load("libX11.so")) { - return; - } - - core::DlLoader libX("libX11.so"); - - pfn_XOpenDisplay fn_XOpenDisplay = - (pfn_XOpenDisplay)libX.lookup("XOpenDisplay"); - pfn_XSetErrorHandler fn_XSetErrorHandler = - (pfn_XSetErrorHandler)libX.lookup("XSetErrorHandler"); - - gContext.mDisplay = fn_XOpenDisplay(nullptr); - if (gContext.mDisplay == nullptr) { - return; - } - - const int visualAttribs[] = { - // clang-format off - GLX_RED_SIZE, 8, - GLX_GREEN_SIZE, 8, - GLX_BLUE_SIZE, 8, - GLX_ALPHA_SIZE, 8, - GLX_DEPTH_SIZE, 24, - GLX_STENCIL_SIZE, 8, - GLX_RENDER_TYPE, GLX_RGBA_BIT, - GLX_DRAWABLE_TYPE, GLX_PBUFFER_BIT, - None - // clang-format on - }; - int fbConfigsCount = 0; - gContext.mFBConfigs = (*fn_glXChooseFBConfig)( - gContext.mDisplay, DefaultScreen(gContext.mDisplay), visualAttribs, - &fbConfigsCount); - if (!gContext.mFBConfigs) { - return; - } - - GLXFBConfig fbConfig = gContext.mFBConfigs[0]; - - if (fn_glXCreateContextAttribsARB == nullptr) { - gContext.mGlCtx = (*fn_glXCreateNewContext)(gContext.mDisplay, fbConfig, - GLX_RGBA_TYPE, nullptr, True); - } else { - // Prevent X from taking down the process if the GL version is not - // supported. - auto oldHandler = - fn_XSetErrorHandler([](Display*, XErrorEvent*) -> int { return 0; }); - for (auto gl_version : core::gl::sVersionSearchOrder) { - // List of name-value pairs. - const int contextAttribs[] = { - // clang-format off - GLX_RENDER_TYPE, GLX_RGBA_TYPE, - GLX_CONTEXT_MAJOR_VERSION_ARB, gl_version.major, - GLX_CONTEXT_MINOR_VERSION_ARB, gl_version.minor, - GLX_CONTEXT_FLAGS_ARB, GLX_CONTEXT_DEBUG_BIT_ARB, - GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB, - None, - // clang-format on - }; - gContext.mGlCtx = - fn_glXCreateContextAttribsARB(gContext.mDisplay, fbConfig, nullptr, - /* direct */ True, contextAttribs); - if (gContext.mGlCtx != nullptr) { - break; - } - } - fn_XSetErrorHandler(oldHandler); - } +namespace { - if (!gContext.mGlCtx) { - return; - } - - const int pbufferAttribs[] = {GLX_PBUFFER_WIDTH, 32, GLX_PBUFFER_HEIGHT, 32, - None}; - - gContext.mPbuffer = - fn_glXCreatePbuffer(gContext.mDisplay, fbConfig, pbufferAttribs); - if (!gContext.mPbuffer) { - return; - } - - fn_glXMakeContextCurrent(gContext.mDisplay, gContext.mPbuffer, - gContext.mPbuffer, gContext.mGlCtx); +device::ABI* abi(device::ABI* abi) { + abi->set_name("x86_64"); + abi->set_os(device::Linux); + abi->set_architecture(device::X86_64); + abi->set_allocated_memory_layout(query::currentMemoryLayout()); + return abi; } -bool createContext() { - if (gContextRefCount++ > 0) { - return true; - } +} // namespace - memset(&gContext, 0, sizeof(gContext)); +namespace query { - if (uname(&gContext.mUbuf) != 0) { - snprintf(gContext.mError, sizeof(gContext.mError), - "uname returned error: %d", errno); - destroyContext(); +bool queryPlatform(PlatformInfo* info, std::string* errorMsg) { + char hostname[HOST_NAME_MAX + 1]; + if (gethostname(hostname, sizeof(hostname)) != 0) { + errorMsg->append("gethostname returned error: " + std::to_string(errno)); return false; } + info->name = hostname; + info->abis.resize(1); + abi(&info->abis[0]); - gContext.mNumCores = sysconf(_SC_NPROCESSORS_CONF); - - if (gethostname(gContext.mHostName, sizeof(gContext.mHostName)) != 0) { - snprintf(gContext.mError, sizeof(gContext.mError), - "gethostname returned error: %d", errno); - destroyContext(); + utsname ubuf; + if (uname(&ubuf) != 0) { + errorMsg->append("uname returned error: " + std::to_string(errno)); return false; } + info->hardwareName = STR_OR_EMPTY(ubuf.machine); - createGlContext(); - - return true; -} - -const char* contextError() { return gContext.mError; } + info->numCpuCores = sysconf(_SC_NPROCESSORS_CONF); -bool hasGLorGLES() { return gContext.mGlCtx != nullptr; } + info->osKind = device::Linux; + info->osName = STR_OR_EMPTY(ubuf.release); + info->osBuild = STR_OR_EMPTY(ubuf.version); -int numABIs() { return 1; } - -void abi(int idx, device::ABI* abi) { - abi->set_name("x86_64"); - abi->set_os(device::Linux); - abi->set_architecture(device::X86_64); - abi->set_allocated_memory_layout(currentMemoryLayout()); -} - -device::ABI* currentABI() { - auto out = new device::ABI(); - abi(0, out); - return out; + return true; } -int cpuNumCores() { return gContext.mNumCores; } - -const char* gpuName() { return ""; } - -const char* gpuVendor() { return ""; } - -const char* instanceName() { return gContext.mHostName; } - -const char* hardwareName() { return STR_OR_EMPTY(gContext.mUbuf.machine); } - -device::OSKind osKind() { return device::Linux; } - -const char* osName() { return STR_OR_EMPTY(gContext.mUbuf.release); } - -const char* osBuild() { return STR_OR_EMPTY(gContext.mUbuf.version); } - -int osMajor() { return 0; } - -int osMinor() { return 0; } - -int osPoint() { return 0; } - -void glDriverPlatform(device::OpenGLDriver*) {} +device::ABI* currentABI() { return abi(new device::ABI()); } device::VulkanProfilingLayers* get_vulkan_profiling_layers() { auto layers = new device::VulkanProfilingLayers(); diff --git a/core/os/device/deviceinfo/cc/osx/query.mm b/core/os/device/deviceinfo/cc/osx/query.mm index 09a59fab9c..7e2f60a4e6 100644 --- a/core/os/device/deviceinfo/cc/osx/query.mm +++ b/core/os/device/deviceinfo/cc/osx/query.mm @@ -17,156 +17,70 @@ #include "../query.h" #import -#import - -#include - -#include #include #include +#include +#include #define STR_OR_EMPTY(x) ((x != nullptr) ? x : "") -namespace query { - -struct Context { - char mError[512]; - NSOpenGLPixelFormat* mGlFmt; - NSOpenGLContext* mGlCtx; - NSOperatingSystemVersion mOsVersion; - int mNumCores; - char* mHwModel; - char mHostName[512]; -}; - -static Context gContext; -static int gContextRefCount = 0; - -void destroyContext() { - if (--gContextRefCount > 0) { - return; - } +namespace { - if (gContext.mHwModel) { - delete[] gContext.mHwModel; - } - if (gContext.mGlFmt) { - [gContext.mGlFmt release]; - gContext.mGlFmt = nullptr; - } - if (gContext.mGlCtx) { - [gContext.mGlCtx release]; - gContext.mGlCtx = nullptr; - } +device::ABI* abi(device::ABI* abi) { + abi->set_name("x86_64"); + abi->set_os(device::OSX); + abi->set_architecture(device::X86_64); + abi->set_allocated_memory_layout(query::currentMemoryLayout()); + return abi; } -void createGlContext() { - NSOpenGLPixelFormatAttribute attributes[] = { - // clang-format off - NSOpenGLPFANoRecovery, - NSOpenGLPFAColorSize, (NSOpenGLPixelFormatAttribute)32, - NSOpenGLPFADepthSize, (NSOpenGLPixelFormatAttribute)24, - NSOpenGLPFAStencilSize, (NSOpenGLPixelFormatAttribute)8, - NSOpenGLPFAAccelerated, - NSOpenGLPFABackingStore, - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, - (NSOpenGLPixelFormatAttribute)0 - // clang-format on - }; - - gContext.mGlFmt = [[NSOpenGLPixelFormat alloc] initWithAttributes:attributes]; - if (gContext.mGlFmt != nullptr) { - gContext.mGlCtx = [[NSOpenGLContext alloc] initWithFormat:gContext.mGlFmt shareContext:nil]; - if (gContext.mGlCtx == nullptr) { - return; - } - - [gContext.mGlCtx makeCurrentContext]; - } -} +} // namespace -bool createContext() { - if (gContextRefCount++ > 0) { - return true; - } +namespace query { - memset(&gContext, 0, sizeof(gContext)); +bool queryPlatform(PlatformInfo* info, std::string* errorMsg) { + char hostname[256]; + if (gethostname(hostname, sizeof(hostname)) != 0) { + errorMsg->append("gethostname returned error: " + std::to_string(errno)); + return false; + } + info->name = hostname; + info->abis.resize(1); + abi(&info->abis[0]); size_t len = 0; int mib[2] = {CTL_HW, HW_MODEL}; - sysctl(mib, 2, nullptr, &len, nullptr, 0); - gContext.mHwModel = new char[len]; - if (sysctl(mib, 2, gContext.mHwModel, &len, nullptr, 0) != 0) { - snprintf(gContext.mError, sizeof(gContext.mError), - "sysctl {CTL_HW, HW_MODEL} returned error: %d", errno); - destroyContext(); + if (sysctl(mib, 2, nullptr, &len, nullptr, 0) != 0) { + errorMsg->append("sysctl {CTL_HW, HW_MODEL} returned error: " + std::to_string(errno)); return false; } - - len = sizeof(gContext.mNumCores); - if (sysctlbyname("hw.logicalcpu_max", &gContext.mNumCores, &len, nullptr, 0) != 0) { - snprintf(gContext.mError, sizeof(gContext.mError), - "sysctlbyname 'hw.logicalcpu_max' returned error: %d", errno); - destroyContext(); + char* hwModel = new char[len]; + if (sysctl(mib, 2, hwModel, &len, nullptr, 0) != 0) { + errorMsg->append("sysctl {CTL_HW, HW_MODEL} returned error: " + std::to_string(errno)); + delete[] hwModel; return false; } + info->hardwareName = hwModel; + delete[] hwModel; - if (gethostname(gContext.mHostName, sizeof(gContext.mHostName)) != 0) { - snprintf(gContext.mError, sizeof(gContext.mError), "gethostname returned error: %d", errno); - destroyContext(); + len = sizeof(info->numCpuCores); + if (sysctlbyname("hw.logicalcpu_max", &info->numCpuCores, &len, nullptr, 0) != 0) { + errorMsg->append("sysctlbyname 'hw.logicalcpu_max' returned error: " + std::to_string(errno)); return false; } - createGlContext(); - - gContext.mOsVersion = [[NSProcessInfo processInfo] operatingSystemVersion]; + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; + info->osKind = device::OSX; + info->osName = "OSX"; + info->osMajor = version.majorVersion; + info->osMinor = version.minorVersion; + info->osPoint = version.patchVersion; return true; } -const char* contextError() { return gContext.mError; } - -bool hasGLorGLES() { return gContext.mGlCtx != nullptr; } - -int numABIs() { return 1; } - -void abi(int idx, device::ABI* abi) { - abi->set_name("x86_64"); - abi->set_os(device::OSX); - abi->set_architecture(device::X86_64); - abi->set_allocated_memory_layout(currentMemoryLayout()); -} - -device::ABI* currentABI() { - auto out = new device::ABI(); - abi(0, out); - return out; -} - -int cpuNumCores() { return gContext.mNumCores; } - -const char* gpuName() { return ""; } - -const char* gpuVendor() { return ""; } - -const char* instanceName() { return gContext.mHostName; } - -const char* hardwareName() { return STR_OR_EMPTY(gContext.mHwModel); } - -device::OSKind osKind() { return device::OSX; } - -const char* osName() { return "OSX"; } - -const char* osBuild() { return ""; } - -int osMajor() { return gContext.mOsVersion.majorVersion; } - -int osMinor() { return gContext.mOsVersion.minorVersion; } - -int osPoint() { return gContext.mOsVersion.patchVersion; } - -void glDriverPlatform(device::OpenGLDriver*) {} +device::ABI* currentABI() { return abi(new device::ABI()); } device::VulkanProfilingLayers* get_vulkan_profiling_layers() { return nullptr; } diff --git a/core/os/device/deviceinfo/cc/query.cpp b/core/os/device/deviceinfo/cc/query.cpp index d84b21b369..c057a4069c 100644 --- a/core/os/device/deviceinfo/cc/query.cpp +++ b/core/os/device/deviceinfo/cc/query.cpp @@ -19,8 +19,6 @@ #include #include -#include - namespace { inline bool isLittleEndian() { @@ -52,7 +50,7 @@ void deviceInstanceID(device::Instance* instance) { auto id = instance->mutable_id(); // Serialize the instance so we can hash it. - auto proto_size = instance->ByteSize(); + auto proto_size = instance->ByteSizeLong(); auto proto_data = new uint8_t[proto_size]; instance->SerializeToArray(proto_data, proto_size); @@ -72,36 +70,61 @@ void deviceInstanceID(device::Instance* instance) { delete[] proto_data; } -void buildDeviceInstance(const query::Option& opt, device::Instance** out) { +std::string getVendorName(uint32_t vendorId) { + // A tiny little PCI-ID database. + // (https://pcisig.com/membership/member-companies) + switch (vendorId) { + case 0x1022: + return "AMD"; + case 0x10DE: + return "NVIDIA"; + case 0x13B5: + return "ARM"; + case 0x1AE0: + return "Google"; + case 0x144D: + return "Samsung"; + case 0x14E4: + return "Broadcom"; + case 0x1F96: + return "Intel"; + case 0x5143: + return "Qualcomm"; + default: + return ""; + } +} + +} // anonymous namespace + +namespace query { + +device::Instance* getDeviceInstance(const Option& opt, std::string* error) { using namespace device; using namespace google::protobuf::io; - if (!query::createContext()) { - return; + PlatformInfo osInfo; + CpuInfo cpuInfo; + if (!query::queryPlatform(&osInfo, error) || + !query::queryCpu(&cpuInfo, error)) { + return nullptr; } // OS auto os = new OS(); - os->set_kind(query::osKind()); - os->set_name(query::osName()); - os->set_build(query::osBuild()); - os->set_major_version(query::osMajor()); - os->set_minor_version(query::osMinor()); - os->set_point_version(query::osPoint()); + os->set_kind(osInfo.osKind); + os->set_name(osInfo.osName); + os->set_build(osInfo.osBuild); + os->set_major_version(osInfo.osMajor); + os->set_minor_version(osInfo.osMinor); + os->set_point_version(osInfo.osPoint); // Instance.Configuration.Drivers auto drivers = new Drivers(); - const char* backupVendor = ""; - const char* backupName = ""; - if (query::hasGLorGLES()) { - // Instance.Configuration.Drivers.OpenGLDriver - auto opengl_driver = new OpenGLDriver(); - query::glDriver(opengl_driver); - drivers->set_allocated_opengl(opengl_driver); - backupVendor = opengl_driver->vendor().c_str(); - backupName = opengl_driver->renderer().c_str(); - } + std::string gpuVendor = ""; + std::string gpuName = ""; + uint32_t gpuDriverVersion = 0u; // Checks if the device supports Vulkan (have Vulkan loader) first, then // populates the VulkanDriver message. @@ -112,9 +135,11 @@ void buildDeviceInstance(const query::Option& opt, device::Instance** out) { } if (opt.vulkan.query_physical_devices()) { query::vkPhysicalDevices(vulkan_driver); - if (strlen(backupName) == 0 && - vulkan_driver->physical_devices_size() > 0) { - backupName = vulkan_driver->physical_devices(0).device_name().c_str(); + if (vulkan_driver->physical_devices_size() > 0) { + gpuVendor = + getVendorName(vulkan_driver->physical_devices(0).vendor_id()); + gpuName = vulkan_driver->physical_devices(0).device_name(); + gpuDriverVersion = vulkan_driver->physical_devices(0).driver_version(); } } drivers->set_allocated_vulkan(vulkan_driver); @@ -122,27 +147,20 @@ void buildDeviceInstance(const query::Option& opt, device::Instance** out) { // Instance.Configuration.Hardware.CPU auto cpu = new CPU(); - cpu->set_name(query::cpuName()); - cpu->set_vendor(query::cpuVendor()); - cpu->set_architecture(query::cpuArchitecture()); - cpu->set_cores(query::cpuNumCores()); + cpu->set_name(cpuInfo.name); + cpu->set_vendor(cpuInfo.vendor); + cpu->set_architecture(cpuInfo.architecture); + cpu->set_cores(osInfo.numCpuCores); // Instance.Configuration.Hardware.GPU auto gpu = new GPU(); - const char* gpuName = query::gpuName(); - const char* gpuVendor = query::gpuVendor(); - if (strlen(gpuName) == 0) { - gpuName = backupName; - } - if (strlen(gpuVendor) == 0) { - gpuVendor = backupVendor; - } gpu->set_name(gpuName); gpu->set_vendor(gpuVendor); + gpu->set_version(gpuDriverVersion); // Instance.Configuration.Hardware auto hardware = new Hardware(); - hardware->set_name(query::hardwareName()); + hardware->set_name(osInfo.hardwareName); hardware->set_allocated_cpu(cpu); hardware->set_allocated_gpu(gpu); @@ -151,9 +169,7 @@ void buildDeviceInstance(const query::Option& opt, device::Instance** out) { configuration->set_allocated_os(os); configuration->set_allocated_hardware(hardware); configuration->set_allocated_drivers(drivers); - for (int i = 0, c = query::numABIs(); i < c; i++) { - query::abi(i, configuration->add_abis()); - } + *configuration->mutable_abis() = {osInfo.abis.begin(), osInfo.abis.end()}; auto perfetto_config = new PerfettoCapability(); auto vulkan_performance_layers = query::get_vulkan_profiling_layers(); @@ -166,37 +182,10 @@ void buildDeviceInstance(const query::Option& opt, device::Instance** out) { // Instance auto instance = new Instance(); - instance->set_name(query::instanceName()); + instance->set_name(osInfo.name); instance->set_allocated_configuration(configuration); deviceInstanceID(instance); - // Blacklist of OS/Hardware version that means we cannot safely - // destroy the context. - // https://github.com/google/gapid/issues/1867 - bool blacklist = false; - if (std::string(gpuName).find("Vega") != std::string::npos && - std::string(query::osName()).find("Windows 10") != std::string::npos) { - blacklist = true; - } - - if (!blacklist) { - query::destroyContext(); - } - - *out = instance; -} - -} // anonymous namespace - -namespace query { - -device::Instance* getDeviceInstance(const Option& opt) { - device::Instance* instance = nullptr; - - // buildDeviceInstance on a separate thread to avoid EGL screwing with the - // currently bound context. - std::thread thread(buildDeviceInstance, opt, &instance); - thread.join(); return instance; } diff --git a/core/os/device/deviceinfo/cc/query.h b/core/os/device/deviceinfo/cc/query.h index 95fecd3ae8..65baf953bd 100644 --- a/core/os/device/deviceinfo/cc/query.h +++ b/core/os/device/deviceinfo/cc/query.h @@ -63,8 +63,10 @@ struct Option { }; // getDeviceInstance returns the device::Instance proto message for the -// current device. It must be freed with delete. -device::Instance* getDeviceInstance(const Option& opt); +// current device. It must be freed with delete. If there is an error +// getting the device info, null is returned and the error string is filled +// with a message. +device::Instance* getDeviceInstance(const Option& opt, std::string* error); // updateVulkanPhysicalDevices modifies the given device::Instance by adding // device::VulkanPhysicalDevice to the device::Instance. If a @@ -94,7 +96,7 @@ bool vkLayersAndExtensions( std::function get_inst_proc_addr = nullptr); // vkPhysicalDevices populates the VulkanPhysicalDevices fields in the -// given device::VulkanDriver and creates a dummy VkDevice. Returns true if +// given device::VulkanDriver and optionally creates a VkDevice. Returns true if // succeeded. If a vkGetInstanceProcAddress function is given, that function // will be used to resolve Vulkan API calls, otherwise Vulkan loader will be // used. @@ -109,50 +111,36 @@ device::VulkanProfilingLayers* get_vulkan_profiling_layers(); // false. bool hasVulkanLoader(); -// hasGLorGLES returns true if there is a OpenGL or OpenGL ES display driver -// that can be used. -bool hasGLorGLES(); - // The functions below are used by getDeviceInstance(), and are implemented // in the target-dependent sub-directories. -bool createContext(); -const char* contextError(); -void destroyContext(); +struct PlatformInfo { + std::string name; + std::vector abis; + std::string hardwareName; + int numCpuCores = 0; // Fetching this is OS specific, not CPU specific. + device::OSKind osKind; + std::string osName; + std::string osBuild; + int osMajor = 0; + int osMinor = 0; + int osPoint = 0; +}; -// The functions below require a context to be created. +bool queryPlatform(PlatformInfo* info, std::string* error); -int numABIs(); -void abi(int idx, device::ABI* abi); device::ABI* currentABI(); device::MemoryLayout* currentMemoryLayout(); - -const char* hardwareName(); - -const char* cpuName(); -const char* cpuVendor(); -device::Architecture cpuArchitecture(); -int cpuNumCores(); - -const char* gpuName(); -const char* gpuVendor(); - -const char* instanceName(); - -// This queries the platform independent GL things. -void glDriver(device::OpenGLDriver*); -// This queries the platform depended GL things. -void glDriverPlatform(device::OpenGLDriver*); - -device::OSKind osKind(); -const char* osName(); -const char* osBuild(); -int osMajor(); -int osMinor(); -int osPoint(); - bool hasAtrace(); +// in cpu.cpp +struct CpuInfo { + std::string name; + std::string vendor; + device::Architecture architecture; +}; +bool queryCpu(CpuInfo* info, std::string* error); + } // namespace query #endif // DEVICEINFO_QUERY_H diff --git a/core/os/device/deviceinfo/cc/windows/query.cpp b/core/os/device/deviceinfo/cc/windows/query.cpp index ef58fa0917..ef079c4196 100644 --- a/core/os/device/deviceinfo/cc/windows/query.cpp +++ b/core/os/device/deviceinfo/cc/windows/query.cpp @@ -16,226 +16,95 @@ #include "../query.h" -#include "core/cc/get_gles_proc_address.h" - -#include #include -#include namespace { -static const char* wndClassName = TEXT("opengl-dummy-window"); - -WNDCLASSEX registerWindowClass() { - WNDCLASSEX wc; - memset(&wc, 0, sizeof(wc)); - wc.cbSize = sizeof(wc); - // We must use CS_OWNDC here if we are to use this window with GL. - // https://www.khronos.org/opengl/wiki/Creating_an_OpenGL_Context_(WGL)#The_Window_Itself - wc.style = CS_OWNDC; - wc.lpfnWndProc = DefWindowProc; - wc.hInstance = GetModuleHandle(0); - wc.hCursor = LoadCursor(0, IDC_ARROW); - wc.lpszMenuName = TEXT(""); - wc.lpszClassName = wndClassName; - RegisterClassEx(&wc); - return wc; -} - -} // anonymous namespace - -namespace query { - -typedef HGLRC(CALLBACK* pfn_wglCreateContext)(HDC); -typedef BOOL(CALLBACK* pfn_wglMakeCurrent)(HDC, HGLRC); -typedef BOOL(CALLBACK* pfn_wglDeleteContext)(HGLRC); - -struct Context { - char mError[512]; - HWND mWnd; - HDC mHDC; - HGLRC mGlCtx; - int mNumCores; - char mHostName[MAX_COMPUTERNAME_LENGTH * 4 + 1]; // Stored as UTF-8 - OSVERSIONINFOEX mOsVersion; - const char* mOsName; - - pfn_wglCreateContext wglCreateContext; - pfn_wglMakeCurrent wglMakeCurrent; - pfn_wglDeleteContext wglDeleteContext; -}; - -static Context gContext; -static int gContextRefCount = 0; - -void destroyContext() { - if (--gContextRefCount > 0) { - return; - } - - if (gContext.mWnd != nullptr) { - DestroyWindow(gContext.mWnd); - } - if (gContext.mGlCtx != nullptr) { - gContext.wglMakeCurrent(gContext.mHDC, 0); - gContext.wglDeleteContext(gContext.mGlCtx); - } -} - -void createGlContext() { - gContext.wglCreateContext = - (pfn_wglCreateContext)core::GetGlesProcAddress("wglCreateContext"); - gContext.wglMakeCurrent = - (pfn_wglMakeCurrent)core::GetGlesProcAddress("wglMakeCurrent"); - gContext.wglDeleteContext = - (pfn_wglDeleteContext)core::GetGlesProcAddress("wglDeleteContext"); - - if (gContext.wglCreateContext == nullptr || - gContext.wglMakeCurrent == nullptr || - gContext.wglDeleteContext == nullptr) { - return; - } - - WNDCLASSEX wc = registerWindowClass(); - gContext.mWnd = CreateWindow(wndClassName, TEXT(""), WS_POPUP, 0, 0, 8, 8, 0, - 0, GetModuleHandle(0), 0); - if (gContext.mWnd == nullptr) { - return; - } - - PIXELFORMATDESCRIPTOR pfd; - memset(&pfd, 0, sizeof(pfd)); - pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); - pfd.nVersion = 1; - pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL; - pfd.iPixelType = PFD_TYPE_RGBA; - pfd.cRedBits = 8; - pfd.cGreenBits = 8; - pfd.cBlueBits = 8; - pfd.cAlphaBits = 8; - pfd.cDepthBits = 24; - pfd.cStencilBits = 8; - pfd.cColorBits = 32; - pfd.iLayerType = PFD_MAIN_PLANE; - gContext.mHDC = GetDC(gContext.mWnd); - SetPixelFormat(gContext.mHDC, ChoosePixelFormat(gContext.mHDC, &pfd), &pfd); - gContext.mGlCtx = gContext.wglCreateContext(gContext.mHDC); - if (gContext.mGlCtx != nullptr) { - gContext.wglMakeCurrent(gContext.mHDC, gContext.mGlCtx); - } - - return; -} - -bool createContext() { - if (gContextRefCount++ > 0) { - return true; - } - - createGlContext(); - - gContext.mOsVersion.dwOSVersionInfoSize = sizeof(gContext.mOsVersion); - GetVersionEx((OSVERSIONINFO*)(&gContext.mOsVersion)); - int major = gContext.mOsVersion.dwMajorVersion; - int minor = gContext.mOsVersion.dwMinorVersion; - int point = gContext.mOsVersion.dwBuildNumber; - bool isNTWorkstation = - (gContext.mOsVersion.wProductType == VER_NT_WORKSTATION); - +std::string getOsName(const OSVERSIONINFOEX& version) { + bool isNTWorkstation = version.wProductType == VER_NT_WORKSTATION; + int major = version.dwMajorVersion; + int minor = version.dwMinorVersion; if (major == 10 && isNTWorkstation) { - gContext.mOsName = "Windows 10"; + return "Windows 10"; } else if (major == 10 && !isNTWorkstation) { - gContext.mOsName = "Windows Server 2016 Technical Preview"; + return "Windows Server 2016 Technical Preview"; } else if (major == 6 && minor == 3 && isNTWorkstation) { - gContext.mOsName = "Windows 8.1"; + return "Windows 8.1"; } else if (major == 6 && minor == 3 && !isNTWorkstation) { - gContext.mOsName = "Windows Server 2012 R2"; + return "Windows Server 2012 R2"; } else if (major == 6 && minor == 2 && isNTWorkstation) { - gContext.mOsName = "Windows 8"; + return "Windows 8"; } else if (major == 6 && minor == 2 && !isNTWorkstation) { - gContext.mOsName = "Windows Server 2012"; + return "Windows Server 2012"; } else if (major == 6 && minor == 1 && isNTWorkstation) { - gContext.mOsName = "Windows 7"; + return "Windows 7"; } else if (major == 6 && minor == 1 && !isNTWorkstation) { - gContext.mOsName = "Windows Server 2008 R2"; + return "Windows Server 2008 R2"; } else if (major == 6 && minor == 0 && isNTWorkstation) { - gContext.mOsName = "Windows Vista"; + return "Windows Vista"; } else if (major == 6 && minor == 0 && !isNTWorkstation) { - gContext.mOsName = "Windows Server 2008"; + return "Windows Server 2008"; } else if (major == 5 && minor == 1) { - gContext.mOsName = "Windows XP"; + return "Windows XP"; } else if (major == 5 && minor == 0) { - gContext.mOsName = "Windows 2000"; + return "Windows 2000"; } else { - gContext.mOsName = ""; + return ""; } +} - SYSTEM_INFO sysInfo; - GetSystemInfo(&sysInfo); - gContext.mNumCores = sysInfo.dwNumberOfProcessors; +device::ABI* abi(device::ABI* abi) { + abi->set_name("x86_64"); + abi->set_os(device::Windows); + abi->set_architecture(device::X86_64); + abi->set_allocated_memory_layout(query::currentMemoryLayout()); + return abi; +} + +} // namespace +namespace query { + +bool queryPlatform(PlatformInfo* info, std::string* errorMsg) { DWORD size = MAX_COMPUTERNAME_LENGTH + 1; WCHAR host_wide[MAX_COMPUTERNAME_LENGTH + 1]; if (!GetComputerNameW(host_wide, &size)) { - snprintf(gContext.mError, sizeof(gContext.mError), - "Couldn't get host name: %d", GetLastError()); + errorMsg->append("Couldn't get host name: " + + std::to_string(GetLastError())); return false; } + char hostName[MAX_COMPUTERNAME_LENGTH * 4 + 1]; // Stored as UTF-8 WideCharToMultiByte(CP_UTF8, // CodePage 0, // dwFlags host_wide, // lpWideCharStr -1, // cchWideChar - gContext.mHostName, // lpMultiByteStr - sizeof(gContext.mHostName), // cbMultiByte + hostName, // lpMultiByteStr + sizeof(hostName), // cbMultiByte nullptr, // lpDefaultChar nullptr // lpUsedDefaultChar ); - return true; -} - -const char* contextError() { return gContext.mError; } + info->name = hostName; + info->abis.resize(1); + abi(&info->abis[0]); -bool hasGLorGLES() { return gContext.mGlCtx != nullptr; } - -int numABIs() { return 1; } + SYSTEM_INFO sysInfo; + GetSystemInfo(&sysInfo); + info->numCpuCores = sysInfo.dwNumberOfProcessors; -void abi(int idx, device::ABI* abi) { - abi->set_name("x86_64"); - abi->set_os(device::Windows); - abi->set_architecture(device::X86_64); - abi->set_allocated_memory_layout(currentMemoryLayout()); -} + info->osKind = device::Windows; + OSVERSIONINFOEX osVersion; + osVersion.dwOSVersionInfoSize = sizeof(osVersion); + GetVersionEx((OSVERSIONINFO*)(&osVersion)); + info->osName = getOsName(osVersion); + info->osMajor = osVersion.dwMajorVersion; + info->osMinor = osVersion.dwMinorVersion; + info->osPoint = osVersion.dwBuildNumber; -device::ABI* currentABI() { - auto out = new device::ABI(); - abi(0, out); - return out; + return true; } -int cpuNumCores() { return gContext.mNumCores; } - -const char* gpuName() { return ""; } - -const char* gpuVendor() { return ""; } - -const char* instanceName() { return gContext.mHostName; } - -const char* hardwareName() { return ""; } - -device::OSKind osKind() { return device::Windows; } - -const char* osName() { return gContext.mOsName; } - -const char* osBuild() { return ""; } - -int osMajor() { return gContext.mOsVersion.dwMajorVersion; } - -int osMinor() { return gContext.mOsVersion.dwMinorVersion; } - -int osPoint() { return gContext.mOsVersion.dwBuildNumber; } - -void glDriverPlatform(device::OpenGLDriver*) {} +device::ABI* currentABI() { return abi(new device::ABI()); } device::VulkanProfilingLayers* get_vulkan_profiling_layers() { return nullptr; } diff --git a/core/os/device/ggp/BUILD.bazel b/core/os/device/ggp/BUILD.bazel deleted file mode 100644 index 8af0bf3bea..0000000000 --- a/core/os/device/ggp/BUILD.bazel +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright (C) 2019 Google Inc. -# -# 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. - -load("@io_bazel_rules_go//go:def.bzl", "go_library") - -go_library( - name = "go_default_library", - srcs = [ - "device.go", - "doc.go", - "parse.go", - ], - importpath = "github.com/google/gapid/core/os/device/ggp", - visibility = ["//visibility:public"], - deps = [ - "//core/event/task:go_default_library", - "//core/log:go_default_library", - "//core/os/device:go_default_library", - "//core/os/device/bind:go_default_library", - "//core/os/device/remotessh:go_default_library", - "//core/os/file:go_default_library", - "//core/os/shell:go_default_library", - ], -) diff --git a/core/os/device/ggp/device.go b/core/os/device/ggp/device.go deleted file mode 100644 index 461051cb8f..0000000000 --- a/core/os/device/ggp/device.go +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (C) 2019 Google Inc. -// -// 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. - -package ggp - -import ( - "bufio" - "bytes" - "context" - "encoding/json" - "fmt" - "os" - "path" - "path/filepath" - "runtime" - "strings" - "sync" - "time" - - "github.com/google/gapid/core/event/task" - "github.com/google/gapid/core/log" - bd "github.com/google/gapid/core/os/device" - "github.com/google/gapid/core/os/device/bind" - "github.com/google/gapid/core/os/device/remotessh" - "github.com/google/gapid/core/os/file" - "github.com/google/gapid/core/os/shell" -) - -// Binding represents an attached ggp ssh client -type Binding struct { - remotessh.Device - Inst string -} - -const ( - // Frequency at which to print scan errors - printScanErrorsEveryNSeconds = 120 -) - -var _ bind.Device = &Binding{} - -var ( - // Registry of all the discovered devices. - registry = bind.NewRegistry() - - // cache is a map of device names to fully resolved bindings. - cache = map[string]*Binding{} - cacheMutex sync.Mutex // Guards cache. -) - -// GGP is the path to the ggp executable. -var GGP file.Path - -// GGPExecutablePath returns the path to the ggp -// executable -func GGPExecutablePath() (file.Path, error) { - if !GGP.IsEmpty() { - return GGP, nil - } - ggpExe := "ggp" - search := []string{ggpExe} - - if ggpSDKPath := os.Getenv("GGP_SDK_PATH"); ggpSDKPath != "" { - search = append(search, - filepath.Join(ggpSDKPath, "dev", "bin", ggpExe)) - } - for _, path := range search { - if p, err := file.FindExecutable(path); err == nil { - GGP = p - return GGP, nil - } - } - - return file.Path{}, fmt.Errorf( - "ggp could not be found from GGP_SDK_PATH or PATH\n"+ - "GGP_SDK_PATH: %v\n"+ - "PATH: %v\n"+ - "search: %v", - os.Getenv("GGP_SDK_PATH"), os.Getenv("PATH"), search) -} - -func ggpDefaultRootConfigPath() (string, error) { - if runtime.GOOS == "windows" { - return os.Getenv("APPDATA"), nil - } - if p := os.Getenv("XDG_CONFIG_HOME"); p != "" { - return path.Clean(p), nil - } - if p := os.Getenv("HOME"); p != "" { - return path.Join(path.Clean(p), ".config"), nil - } - return "", fmt.Errorf("Can not find environment") -} - -type GGPConfiguration struct { - remotessh.Configuration - Inst string -} - -func getConfigs(ctx context.Context) ([]GGPConfiguration, error) { - configs := []GGPConfiguration{} - - ggpPath, err := GGPExecutablePath() - if err != nil { - return nil, log.Errf(ctx, err, "Could not find ggp executable to list instances") - } - cli := ggpPath.System() - - cmd := shell.Command(cli, "instance", "list") - instanceListOutBuf := &bytes.Buffer{} - instanceListErrBuf := &bytes.Buffer{} - - if err := cmd.Capture(instanceListOutBuf, instanceListErrBuf).Run(ctx); err != nil { - return nil, err - } - - t, err := ParseListOutput(instanceListOutBuf) - if err != nil { - return nil, log.Errf(ctx, err, "parse instance list") - } - for _, inf := range t.Rows { - if inf[3] != "RESERVED" && inf[3] != "IN_USE" { - continue - } - sshInitOutBuf := &bytes.Buffer{} - sshInitErrBuf := &bytes.Buffer{} - - if err := shell.Command(cli, "ssh", "init", "--instance", inf[1], "-s").Capture(sshInitOutBuf, sshInitErrBuf).Run(ctx); err != nil { - log.W(ctx, "'ggp ssh init --instance %v -s' finished with error: %v", inf[1], err) - continue - } - if sshInitErrBuf.Len() != 0 { - log.W(ctx, "'ggp ssh init --instance %v -s' finished with error: %v", inf[1], sshInitErrBuf.String()) - continue - } - envs := []string{ - "YETI_DISABLE_GUEST_ORC=1", - "YETI_DISABLE_STREAMER=1", - } - cfg := remotessh.Configuration{ - Name: inf[0], - Env: envs, - } - if err := json.Unmarshal(sshInitOutBuf.Bytes(), &cfg); err != nil { - log.W(ctx, "Failed at unmarshaling 'ggp ssh init --instance %v -s' output, fallback to use default config, err: %v", inf[1], err) - } - configs = append(configs, GGPConfiguration{cfg, inf[1]}) - } - return configs, nil -} - -// Monitor updates the registry with devices that are added and removed at the -// specified interval. Monitor returns once the context is cancelled. -func Monitor(ctx context.Context, r *bind.Registry, interval time.Duration) error { - unlisten := registry.Listen(bind.NewDeviceListener(r.AddDevice, r.RemoveDevice)) - defer unlisten() - - for _, d := range registry.Devices() { - r.AddDevice(ctx, d) - } - - var lastErrorPrinted time.Time - - for { - configs, err := getConfigs(ctx) - if err != nil { - return err - } - if err := scanDevices(ctx, configs); err != nil { - if time.Since(lastErrorPrinted).Seconds() > printScanErrorsEveryNSeconds { - log.E(ctx, "Couldn't scan devices: %v", err) - lastErrorPrinted = time.Now() - } - } else { - lastErrorPrinted = time.Time{} - } - - select { - case <-task.ShouldStop(ctx): - return nil - case <-time.After(interval): - } - } -} - -// Devices returns the list of attached GGP devices. -func Devices(ctx context.Context) ([]bind.Device, error) { - configs, err := getConfigs(ctx) - - if err != nil { - return nil, err - } - - if err := scanDevices(ctx, configs); err != nil { - return nil, err - } - devs := registry.Devices() - out := make([]bind.Device, len(devs)) - for i, d := range devs { - out[i] = d - } - return out, nil -} - -func deviceStillConnected(ctx context.Context, d *Binding) bool { - return d.Status(ctx) == bind.Status_Online -} - -func scanDevices(ctx context.Context, configurations []GGPConfiguration) error { - cacheMutex.Lock() - defer cacheMutex.Unlock() - allConfigs := make(map[string]bool) - - for _, cfg := range configurations { - allConfigs[cfg.Name] = true - - // If this device already exists, see if we - // can/have to remove it - if cached, ok := cache[cfg.Name]; ok { - if !deviceStillConnected(ctx, cached) { - delete(cache, cfg.Name) - registry.RemoveDevice(ctx, cached) - } - } else { - if device, err := remotessh.GetConnectedDevice(ctx, cfg.Configuration); err == nil { - dev := Binding{ - Device: device, - Inst: cfg.Inst, - } - device.Instance().Configuration.OS.Kind = bd.Stadia - registry.AddDevice(ctx, dev) - cache[cfg.Name] = &dev - } - } - } - - for name, dev := range cache { - if _, ok := allConfigs[name]; !ok { - delete(cache, name) - registry.RemoveDevice(ctx, *dev) - } - } - return nil -} - -// ListExecutables lists all executables. -// On GGP, executables may not have the executable bit set, -// so treat any file as executable -func (b Binding) ListExecutables(ctx context.Context, inPath string) ([]string, error) { - if inPath == "" { - inPath = b.GetURIRoot() - } - // 'find' may partially succeed. Redirect the error messages to /dev/null, only - // process the found files. - files, _ := b.Shell("find", `"`+inPath+`"`, "-mindepth", "1", "-maxdepth", "1", "-type", "f", "-printf", `%f\\n`, "2>/dev/null").Call(ctx) - scanner := bufio.NewScanner(strings.NewReader(files)) - out := []string{} - for scanner.Scan() { - _, file := path.Split(scanner.Text()) - out = append(out, file) - } - return out, nil -} - -// DefaultReplayCacheDir returns the default replay resource cache directory -// on a GGP device -func (b Binding) DefaultReplayCacheDir() string { - return "/mnt/developer/ggp/gapid/replay_cache" -} diff --git a/core/os/device/ggp/doc.go b/core/os/device/ggp/doc.go deleted file mode 100644 index b343496299..0000000000 --- a/core/os/device/ggp/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (C) 2019 Google Inc. -// -// 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. - -// Package ggp contains code for binding to and controlling Stadia instances. -// https://stadia.dev -package ggp diff --git a/core/os/device/ggp/parse.go b/core/os/device/ggp/parse.go deleted file mode 100644 index fe3fa1780c..0000000000 --- a/core/os/device/ggp/parse.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright (C) 2019 Google Inc. -// -// 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. - -package ggp - -import ( - "bufio" - "bytes" - "fmt" - "io" - "strings" -) - -// ParseList contains the output from ggp xxx list command -type ParsedList struct { - Header []string - Rows [][]string -} - -// ColumnByName returns the content of the table for the specified column in -// a list of string. -func (t ParsedList) ColumnByName(name string) ([]string, error) { - if len(t.Header) == 0 { - return []string{}, nil - } - ci := -1 - for i, h := range t.Header { - if h == name { - ci = i - } - } - if ci == -1 { - return nil, fmt.Errorf("Could not find column with name: %v", name) - } - ret := make([]string, len(t.Rows)) - for i, r := range t.Rows { - ret[i] = r[ci] - } - return ret, nil -} - -// ParseListOutput parses the output from ggp xxx list command. -// For example: -// blablabla -// header1 header2 header3 -// ======= ======= ======= -// value1 value2 value3 -func ParseListOutput(stdout *bytes.Buffer) (*ParsedList, error) { - reader := bufio.NewReader(stdout) - schemaLine := "" - contentLines := []string{} - fieldIndices := []int{} - for { - line, err := reader.ReadString('\n') - if err == io.EOF { - break - } - if err != nil { - break - } - line = strings.TrimSuffix(line, "\n") - if strings.HasPrefix(line, "no results") { - return &ParsedList{}, nil - } - if strings.HasPrefix(line, "==") { - line = strings.TrimSpace(line) - for i, _ := range line { - if i == 0 { - fieldIndices = append(fieldIndices, i) - continue - } - if line[i] == '=' && line[i-1] == ' ' { - fieldIndices = append(fieldIndices, i) - } - if line[i] != '=' && line[i] != ' ' { - return nil, fmt.Errorf("Separator line contains character other than '=' and blankspace") - } - } - continue - } - if len(fieldIndices) == 0 { - schemaLine = line - } else { - contentLines = append(contentLines, line) - } - } - - if len(schemaLine) == 0 { - return nil, fmt.Errorf("No table header (Column names) found") - } - - extractFieldsFromLine := func(line string) ([]string, error) { - ret := make([]string, 0, len(fieldIndices)) - for i, _ := range fieldIndices { - start := fieldIndices[i] - var end int - if i == len(fieldIndices)-1 { - end = len(line) - } else { - end = fieldIndices[i+1] - } - if start >= len(line) || end > len(line) { - return nil, fmt.Errorf("Unexpected length of line: %v, substr [%v:%d] failed", len(line), start, end) - } - ret = append(ret, strings.TrimSpace(line[start:end])) - } - return ret, nil - } - - schema, err := extractFieldsFromLine(schemaLine) - if err != nil { - return nil, err - } - rows := make([][]string, len(contentLines)) - for i, l := range contentLines { - fields, err := extractFieldsFromLine(l) - if err != nil { - return nil, err - } - rows[i] = fields - } - return &ParsedList{ - Header: schema, - Rows: rows, - }, nil -} diff --git a/core/os/device/gpu_counter_descriptor.proto b/core/os/device/gpu_counter_descriptor.proto index da838a4d5b..f3742437bb 100644 --- a/core/os/device/gpu_counter_descriptor.proto +++ b/core/os/device/gpu_counter_descriptor.proto @@ -29,6 +29,18 @@ option go_package = "github.com/google/gapid/core/os/device"; // This message is sent by a GPU counter producer to specify the counters // available in the hardware. message GpuCounterDescriptor { + // Logical groups for a counter. This is used in the UI to present the + // related counters together. + enum GpuCounterGroup { + UNCLASSIFIED = 0; + SYSTEM = 1; + VERTICES = 2; + FRAGMENTS = 3; + PRIMITIVES = 4; + MEMORY = 5; // Includes counters relating to caching and bandwidth. + COMPUTE = 6; + }; + message GpuCounterSpec { uint32 counter_id = 1; string name = 2; @@ -37,10 +49,11 @@ message GpuCounterDescriptor { oneof peak_value { int64 int_peak_value = 5; double double_peak_value = 6; - }; + } repeated MeasureUnit numerator_units = 7; repeated MeasureUnit denominator_units = 8; bool select_by_default = 9; + repeated GpuCounterGroup groups = 10; } repeated GpuCounterSpec specs = 1; @@ -50,7 +63,7 @@ message GpuCounterDescriptor { message GpuCounterBlock { // required. Unique ID for the counter group. uint32 block_id = 1; - // Number of counters supported by the block. No limit if unset. + // optional. Number of counters supported by the block. No limit if unset. uint32 block_capacity = 2; // optional. Name of block. string name = 3; @@ -73,6 +86,7 @@ message GpuCounterDescriptor { // command buffer. bool supports_instrumented_sampling = 5; + // next id: 41 enum MeasureUnit { NONE = 0; diff --git a/core/os/device/host/BUILD.bazel b/core/os/device/host/BUILD.bazel index 43d667ed19..816637a687 100644 --- a/core/os/device/host/BUILD.bazel +++ b/core/os/device/host/BUILD.bazel @@ -37,8 +37,8 @@ go_library( go_test( name = "go_default_test", srcs = ["host_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/data/id:go_default_library", "//core/log:go_default_library", diff --git a/core/os/device/host/host_darwin.go b/core/os/device/host/host_darwin.go index d6871207d0..a23dd69b38 100644 --- a/core/os/device/host/host_darwin.go +++ b/core/os/device/host/host_darwin.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build darwin +//go:build darwin package host diff --git a/core/os/device/host/host_linux.go b/core/os/device/host/host_linux.go index 33d54497c5..5452c81052 100644 --- a/core/os/device/host/host_linux.go +++ b/core/os/device/host/host_linux.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux +//go:build linux package host diff --git a/core/os/device/host/host_windows.go b/core/os/device/host/host_windows.go index cb815a9cd8..4288a6dbdd 100644 --- a/core/os/device/host/host_windows.go +++ b/core/os/device/host/host_windows.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build windows +//go:build windows package host diff --git a/core/os/device/remotessh/BUILD.bazel b/core/os/device/remotessh/BUILD.bazel index ba1f9e6e2b..dcfdd62b54 100644 --- a/core/os/device/remotessh/BUILD.bazel +++ b/core/os/device/remotessh/BUILD.bazel @@ -46,8 +46,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["configuration_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/log:go_default_library", ], diff --git a/core/os/device/remotessh/commands.go b/core/os/device/remotessh/commands.go index 4886b9d348..bcd735c353 100644 --- a/core/os/device/remotessh/commands.go +++ b/core/os/device/remotessh/commands.go @@ -137,9 +137,13 @@ func (t sshShellTarget) String() string { func (b binding) Status(ctx context.Context) bind.Status { _, err := b.Shell("echo", "Hello World").Call(ctx) if err != nil { - return bind.Status_Offline + return bind.Offline } - return bind.Status_Online + return bind.Online +} + +func (b binding) IsLocal(ctx context.Context) (bool, error) { + return false, nil } // Shell implements the Device interface returning commands that will error if run. @@ -167,7 +171,7 @@ func (b binding) createWindowsTempDirectory(ctx context.Context) (string, app.Cl // full path, and a function that can be called to clean up the directory. func (b binding) TempDir(ctx context.Context) (string, app.Cleanup, error) { switch b.os { - case device.Linux, device.OSX, device.Stadia: + case device.Linux, device.OSX: return b.createPosixTempDirectory(ctx) case device.Windows: return b.createWindowsTempDirectory(ctx) @@ -211,8 +215,7 @@ func (b binding) PushFile(ctx context.Context, source, dest string) error { // If we are on windows pushing to Posix, we lose the executable // bit, get it back. if (b.os == device.Linux || - b.os == device.OSX || - b.os == device.Stadia) && + b.os == device.OSX) && runtime.GOOS == "windows" { mode |= 0550 } diff --git a/core/os/device/remotessh/device.go b/core/os/device/remotessh/device.go index 444a74ac22..43651734d8 100644 --- a/core/os/device/remotessh/device.go +++ b/core/os/device/remotessh/device.go @@ -41,7 +41,7 @@ import ( // Device extends the bind.Device interface with capabilities specific to // remote SSH clients type Device interface { - bind.Device + bind.Desktop // PullFile will transfer the remote file at sourcePath to the local // machine at destPath PullFile(ctx context.Context, sourcePath, destPath string) error @@ -109,7 +109,7 @@ func newBinding(conn *ssh.Client, conf *Configuration, env *shell.Env) *binding Serial: "", Configuration: &device.Configuration{}, }, - LastStatus: bind.Status_Online, + LastStatus: bind.Online, }, } return b @@ -250,7 +250,7 @@ func scanDevices(ctx context.Context, configurations []Configuration) error { } func deviceStillConnected(ctx context.Context, d *binding) bool { - return d.Status(ctx) == bind.Status_Online + return d.Status(ctx) == bind.Online } // getSSHAgent returns a connection to a local SSH agent, if one exists. diff --git a/core/os/file/BUILD.bazel b/core/os/file/BUILD.bazel index 17a77435e6..53527b39cc 100644 --- a/core/os/file/BUILD.bazel +++ b/core/os/file/BUILD.bazel @@ -41,6 +41,9 @@ go_test( "path_test.go", "windows_test.go", ], - embed = [":go_default_library"], - deps = ["//core/assert:go_default_library"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + "//core/log:go_default_library", + ], ) diff --git a/core/os/file/path.go b/core/os/file/path.go index 956d197f80..27e4401f5b 100644 --- a/core/os/file/path.go +++ b/core/os/file/path.go @@ -16,12 +16,14 @@ package file import ( "encoding/json" + "fmt" "io/ioutil" "net/url" "os" "os/exec" "os/user" "path/filepath" + "runtime" "strings" "time" ) @@ -55,6 +57,16 @@ func Temp() (Path, error) { return Abs(p.Name()), nil } +// TempWithExt creates a new temp file with the given name and extension and returns its path. +func TempWithExt(name string, ext string) (Path, error) { + p, err := ioutil.TempFile("", fmt.Sprintf("%s*.%s", name, ext)) + if err != nil { + return Path{}, err + } + p.Close() + return Abs(p.Name()), nil +} + // ExecutablePath returns the path to the running executable. func ExecutablePath() Path { path := Abs(os.Args[0]) @@ -88,6 +100,16 @@ func (p Path) IsDir() bool { return info != nil && info.IsDir() } +// IsExecutable returns true if the file at p.value exists, is executable +// and is not a directory. +func (p Path) IsExecutable() bool { + if runtime.GOOS == "windows" { + return true + } + info := p.Info() + return info != nil && !info.IsDir() && (info.Mode().Perm()&0111) != 0 +} + // System returns the full absolute path using the system separator. func (p Path) System() string { return p.value } diff --git a/core/os/file/path_test.go b/core/os/file/path_test.go index 16413d8a00..f2012d2e55 100644 --- a/core/os/file/path_test.go +++ b/core/os/file/path_test.go @@ -15,9 +15,13 @@ package file_test import ( + "io/ioutil" + "os" + "runtime" "testing" "github.com/google/gapid/core/assert" + "github.com/google/gapid/core/log" "github.com/google/gapid/core/os/file" ) @@ -50,3 +54,36 @@ func TestPathContains(t *testing.T) { Test(test.dir.Contains(test.file) == test.expected) } } + +func TestIsExecutable(t *testing.T) { + ctx := log.Testing(t) + var ( + tmpdir string + err error + tmpfile *os.File + ) + tmpdir, err = ioutil.TempDir("", "path_test-") + assert.For(ctx, "Temp directory").ThatError(err).Succeeded() + + path := file.Abs(tmpdir) + assert.For(ctx, "Temp directory").ThatBoolean(path.IsExecutable()).IsFalse() + + tmpfile, err = ioutil.TempFile(tmpdir, "path_test_file-") + tmpfile.Close() + + if runtime.GOOS == "windows" { + assert.For(ctx, "Windows IsExecutable").ThatBoolean(path.IsExecutable()).IsTrue() + } else { + err = os.Chmod(tmpfile.Name(), 0600) + assert.For(ctx, "Chmod temp file").ThatError(err).Succeeded() + assert.For(ctx, "Chmod temp file").ThatBoolean(path.IsExecutable()).IsFalse() + + err = os.Chmod(tmpfile.Name(), 0755) + assert.For(ctx, "Chmod temp file executable").ThatError(err).Succeeded() + path = file.Abs(tmpfile.Name()) + assert.For(ctx, "Chmod temp file executable").ThatBoolean(path.IsExecutable()).IsTrue() + } + + os.Remove(tmpfile.Name()) + os.Remove(tmpdir) +} diff --git a/core/os/file/unix.go b/core/os/file/unix.go index 23869da88c..d8d8d332fc 100644 --- a/core/os/file/unix.go +++ b/core/os/file/unix.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build !windows +//go:build !windows package file diff --git a/core/os/file/windows.go b/core/os/file/windows.go index 62fe5fb39e..1116175aef 100644 --- a/core/os/file/windows.go +++ b/core/os/file/windows.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build windows +//go:build windows package file diff --git a/core/os/file/windows_test.go b/core/os/file/windows_test.go index d12ffee804..62bb55bd81 100644 --- a/core/os/file/windows_test.go +++ b/core/os/file/windows_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build windows +//go:build windows package file_test diff --git a/core/os/flock/debug.test b/core/os/flock/debug.test deleted file mode 100755 index e8f3c2fef9..0000000000 Binary files a/core/os/flock/debug.test and /dev/null differ diff --git a/core/os/flock/flock_unix.go b/core/os/flock/flock_unix.go index 2d52a6b9d0..30c0d8600d 100644 --- a/core/os/flock/flock_unix.go +++ b/core/os/flock/flock_unix.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux darwin +//go:build linux || darwin package flock diff --git a/core/os/flock/flock_windows.go b/core/os/flock/flock_windows.go index 2e12d4c720..69414f90b5 100644 --- a/core/os/flock/flock_windows.go +++ b/core/os/flock/flock_windows.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build windows +//go:build windows package flock diff --git a/core/os/fuchsia/BUILD.bazel b/core/os/fuchsia/BUILD.bazel new file mode 100644 index 0000000000..4e26f24d41 --- /dev/null +++ b/core/os/fuchsia/BUILD.bazel @@ -0,0 +1,29 @@ +# Copyright (C) 2021 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["device.go"], + importpath = "github.com/google/gapid/core/os/fuchsia", + visibility = ["//visibility:public"], + deps = [ + "//core/event/task:go_default_library", + "//core/os/device/bind:go_default_library", + "//core/os/file:go_default_library", + "//core/os/shell:go_default_library", + "//gapis/service:go_default_library", + ], +) diff --git a/core/os/fuchsia/device.go b/core/os/fuchsia/device.go new file mode 100644 index 0000000000..83ed141908 --- /dev/null +++ b/core/os/fuchsia/device.go @@ -0,0 +1,45 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package fuchsia + +import ( + "context" + + "github.com/google/gapid/core/event/task" + "github.com/google/gapid/core/os/device/bind" + "github.com/google/gapid/core/os/file" + "github.com/google/gapid/core/os/shell" + "github.com/google/gapid/gapis/service" +) + +// Device extends the bind.Device interface with capabilities specific to Fuchsia devices. +type Device interface { + bind.Device + + // DeviceInfo returns a map of properties for this device. + DeviceInfo(ctx context.Context) (map[string]string, error) + + // Command is a helper that builds a shell.Cmd with the device as its target. + Command(name string, args ...string) shell.Cmd + + // Return string array of trace providers. + TraceProviders(ctx context.Context) ([]string, error) + + // StartTrace starts a Fuchsia trace. + StartTrace(ctx context.Context, traceOptions *service.TraceOptions, traceFile file.Path, stop task.Signal, ready task.Task) error + + // StopTrace stops a Fuchsia trace. + StopTrace(ctx context.Context, traceFile file.Path) error +} diff --git a/core/os/fuchsia/ffx/BUILD.bazel b/core/os/fuchsia/ffx/BUILD.bazel new file mode 100644 index 0000000000..b3330f2a39 --- /dev/null +++ b/core/os/fuchsia/ffx/BUILD.bazel @@ -0,0 +1,47 @@ +# Copyright (C) 2021 Google Inc. +# +# 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. + +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "bind.go", + "device.go", + ], + importpath = "github.com/google/gapid/core/os/fuchsia/ffx", + visibility = ["//visibility:public"], + deps = [ + "//core/event/task:go_default_library", + "//core/fault:go_default_library", + "//core/log:go_default_library", + "//core/os/device:go_default_library", + "//core/os/device/bind:go_default_library", + "//core/os/file:go_default_library", + "//core/os/fuchsia:go_default_library", + "//core/os/shell:go_default_library", + "//gapis/service:go_default_library", + ], +) + +go_test( + name = "go_default_test", + size = "small", + srcs = ["device_test.go"], + deps = [ + ":go_default_library", + "//core/assert:go_default_library", + "//core/log:go_default_library", + ], +) diff --git a/core/os/fuchsia/ffx/bind.go b/core/os/fuchsia/ffx/bind.go new file mode 100644 index 0000000000..9c16ad2755 --- /dev/null +++ b/core/os/fuchsia/ffx/bind.go @@ -0,0 +1,224 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package ffx + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strconv" + "strings" + + "github.com/google/gapid/core/event/task" + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/device/bind" + "github.com/google/gapid/core/os/file" + "github.com/google/gapid/core/os/fuchsia" + "github.com/google/gapid/core/os/shell" + "github.com/google/gapid/gapis/service" +) + +// binding represents an attached Fuchsia device. +type binding struct { + bind.Simple +} + +// verify that binding implements Device +var _ fuchsia.Device = (*binding)(nil) + +// FFX is the path to the ffx executable, or an empty string if the ffx +// executable was not found. +var FFX file.Path + +func ffx() (file.Path, error) { + if !FFX.IsEmpty() { + return FFX, nil + } + + if ffx_env := os.Getenv("FUCHSIA_FFX_PATH"); ffx_env != "" { + FFX = file.Abs(ffx_env) + if !FFX.IsExecutable() { + return file.Path{}, fmt.Errorf("ffx path: %s is not executable", FFX) + } + return FFX, nil + } + + return file.Path{}, fmt.Errorf("FUCHSIA_FFX_PATH is not set. " + + "The \"ffx\" tool, from the Fuchsia SDK, is required for Fuchsia device profiling.\n") +} + +// DeviceInfo implements the fuchsia.Device interface. +func (b *binding) DeviceInfo(ctx context.Context) (map[string]string, error) { + stdout, err := b.Command("target", "show", "--json").Call(ctx) + if err != nil { + return nil, log.Errf(ctx, err, "FFX command failed: "+stdout) + } + + info := []map[string]interface{}{} + if err := json.Unmarshal([]byte(stdout), &info); err != nil { + return nil, err + } + + properties := map[string]string{} + warn := false + for _, parent := range info { + group, _ := parent["label"].(string) + if children, ok := parent["child"].([]interface{}); ok { + for _, child := range children { + if child, ok := child.(map[string]interface{}); ok { + label, _ := child["label"].(string) + value, _ := child["value"].(string) + + if value = strings.TrimSpace(value); value != "" { + properties[group+"."+label] = value + } + } else { + warn = true + } + } + } else { + warn = true + } + } + + if warn { + log.W(ctx, "Unexpected JSON output for device info command: \n%s", stdout) + } + return properties, nil +} + +// Command implements the fuchsia.Device interface. +func (b *binding) Command(name string, args ...string) shell.Cmd { + return shell.Command(name, args...).On(deviceTarget{b}) +} + +func (b *binding) augmentFFXCommand(cmd shell.Cmd) (shell.Cmd, error) { + exe, err := ffx() + if err != nil { + return cmd, err + } + + // Adjust the ffx command to use a specific target: + // "ffx -t " + old := cmd.Args + cmd.Args = make([]string, 0, len(old)+4) + cmd.Args = append(cmd.Args, "-t", b.To.Serial) + cmd.Args = append(cmd.Args, cmd.Name) + cmd.Args = append(cmd.Args, old...) + cmd.Name = exe.System() + fmt.Println(cmd) + + // And delegate to the normal local target + return cmd, nil +} + +// TraceProviders implements the fuchsia.Device interface. +func (b *binding) TraceProviders(ctx context.Context) ([]string, error) { + exe, err := ffx() + if err != nil { + return nil, err + } + + cmd, err := b.augmentFFXCommand(shell.Command(exe.System(), "trace", "list-providers")) + + if err != nil { + return nil, err + } + + providersStdOut, err := cmd.Call(ctx) + + if strings.Contains(providersStdOut, "No devices found") { + return nil, ErrNoDeviceList + } + lines := strings.Split(providersStdOut, "\n") + var providers []string + for _, line := range lines { + if strings.HasPrefix(line, "- ") { + tokens := strings.Split(line, " ") + if len(tokens) == 2 { + providers = append(providers, tokens[1]) + } else { + return nil, ErrTraceProvidersFormat + } + } + } + return providers, nil +} + +// StartTrace implements the fuchsia.Device interface and starts a Fuchsia trace. +func (b *binding) StartTrace(ctx context.Context, options *service.TraceOptions, traceFile file.Path, stop task.Signal, ready task.Task) error { + var categoriesArg string + + // Initialize shell command. + cmd := b.Command("trace", "start", "--output", traceFile.System()) + + // Extract trace options and append arguments to command. + if options != nil { + if durationSecs := int(options.Duration); durationSecs > 0 { + cmd = cmd.With("--duration", strconv.Itoa(durationSecs)) + } + + // ffx expects a comma delimited list of trace categories. + if fuchsiaConfig := options.GetFuchsiaTraceConfig(); fuchsiaConfig != nil { + categoriesArg = strings.Join(fuchsiaConfig.Categories, ",") + if len(categoriesArg) > 0 { + cmd = cmd.With("--categories", categoriesArg) + } + } + } + + stdout, err := cmd.Call(ctx) + + if err != nil { + return log.Err(ctx, err, stdout) + } + + if strings.Contains(stdout, "No devices found") { + return ErrNoDeviceList + } + return nil +} + +// StopTrace implements the fuchsia.Device interface and stops a Fuchsia trace. +func (b *binding) StopTrace(ctx context.Context, traceFile file.Path) error { + cmd := b.Command("trace", "stop", "--output", traceFile.System()) + + stdout, err := cmd.Call(ctx) + if err != nil { + return log.Err(ctx, err, stdout) + } + + if strings.Contains(stdout, "No devices found") { + return ErrNoDeviceList + } + + return nil +} + +type deviceTarget struct{ b *binding } + +func (t deviceTarget) Start(cmd shell.Cmd) (shell.Process, error) { + cmd, err := t.b.augmentFFXCommand(cmd) + if err != nil { + return nil, err + } + + return shell.LocalTarget.Start(cmd) +} + +func (t deviceTarget) String() string { + return "command:" + t.b.String() +} diff --git a/core/os/fuchsia/ffx/device.go b/core/os/fuchsia/ffx/device.go new file mode 100644 index 0000000000..4994737a75 --- /dev/null +++ b/core/os/fuchsia/ffx/device.go @@ -0,0 +1,235 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package ffx + +import ( + "context" + "encoding/json" + "strings" + "sync" + "time" + + "github.com/google/gapid/core/event/task" + "github.com/google/gapid/core/fault" + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/device" + "github.com/google/gapid/core/os/device/bind" + "github.com/google/gapid/core/os/fuchsia" + "github.com/google/gapid/core/os/shell" +) + +const ( + // Frequency at which to print scan errors. + printScanErrorsEveryNSeconds = 120 + // ErrNoDeviceList May be returned if the ffx fails to return a device list when asked. + ErrNoDeviceList = fault.Const("Device list not returned") + // ErrInvalidDeviceList May be returned if the device list could not be parsed. + ErrInvalidDeviceList = fault.Const("Could not parse device list") + // Tracing file couldn't be written to. + ErrTraceFilePermissions = fault.Const("Tracing file permissions") + // Start tracing command failed. + ErrStartTrace = fault.Const("Start trace failed") + // Trace providers command failed. + ErrTraceProviders = fault.Const("Trace providers failed") + // Trace providers format. + ErrTraceProvidersFormat = fault.Const("Trace providers format") +) + +var ( + // cache is a map of device serials to fully resolved bindings. + cache = map[string]*binding{} + cacheMutex sync.Mutex // Guards cache. + + // Registry of all the discovered devices. + registry = bind.NewRegistry() +) + +// DeviceList is a list of devices. +type DeviceList []fuchsia.Device + +// Devices returns the list of attached Fuchsia devices. +func Devices(ctx context.Context) (DeviceList, error) { + if err := scanDevices(ctx); err != nil { + return nil, err + } + devs := registry.Devices() + deviceList := make(DeviceList, len(devs)) + for i, d := range devs { + deviceList[i] = d.(fuchsia.Device) + } + return deviceList, nil +} + +// Monitor updates the registry with devices that are added and removed at the +// specified interval. Monitor returns once the context is cancelled. +func Monitor(ctx context.Context, r *bind.Registry, interval time.Duration) error { + unlisten := registry.Listen(bind.NewDeviceListener(r.AddDevice, r.RemoveDevice)) + defer unlisten() + + for _, d := range registry.Devices() { + r.AddDevice(ctx, d) + } + + lastErrorPrinted := time.Now() + for { + err := scanDevices(ctx) + if err != nil { + if time.Since(lastErrorPrinted).Seconds() > printScanErrorsEveryNSeconds { + log.E(ctx, "Couldn't scan Fuchsia devices: %v", err) + lastErrorPrinted = time.Now() + } + } else { + lastErrorPrinted = time.Time{} + } + + select { + case <-task.ShouldStop(ctx): + return nil + case <-time.After(interval): + } + } +} + +func newDevice(ctx context.Context, serial string) (*binding, error) { + log.I(ctx, "Fuchsia Serial: %v", serial) + d := &binding{ + Simple: bind.Simple{ + To: &device.Instance{ + Serial: serial, + Name: "Fuchsia", + Configuration: &device.Configuration{OS: &device.OS{Kind: device.Fuchsia}}, + }, + LastStatus: bind.Online, + }, + } + + properties, err := d.DeviceInfo(ctx) + if err != nil { + return nil, log.Err(ctx, err, "Failed getting device info") + } + + product, _ := properties["build.product"] + board, _ := properties["build.board"] + if product != "" { + if board != "" { + d.To.Configuration.OS.Name = product + "." + board + } else { + d.To.Configuration.OS.Name = product + } + } else if board != "" { + d.To.Configuration.OS.Name = board + } + + if version, ok := properties["build.version"]; ok { + d.To.Configuration.OS.Build = version + } + + if name, ok := properties["product.name"]; ok { + d.To.Configuration.Hardware = &device.Hardware{Name: name} + d.To.Name = name + } else if model := properties["product.model"]; ok { + d.To.Configuration.Hardware = &device.Hardware{Name: model} + d.To.Name = model + } + + // TODO: fill in rest of d.To.Configuration defined in device.proto + + d.Instance().GenID() + + return d, nil +} + +// scanDevices returns the list of attached Fuchsia devices. +func scanDevices(ctx context.Context) error { + exe, err := ffx() + if err != nil { + return log.Err(ctx, err, "") + } + stdout, err := shell.Command(exe.System(), "target", "list", "-f", "json").Call(ctx) + if err != nil { + return err + } + if strings.Contains(stdout, "No devices found") { + return ErrNoDeviceList + } + parsed, err := ParseDevices(ctx, stdout) + if err != nil { + return err + } + + cacheMutex.Lock() + defer cacheMutex.Unlock() + + for serial, _ := range parsed { + if _, ok := cache[serial]; !ok { + device, err := newDevice(ctx, serial) + if err != nil { + return err + } + cache[serial] = device + registry.AddDevice(ctx, device) + } + } + + // Remove cached results for removed devices. + for serial, cached := range cache { + if _, found := parsed[serial]; !found { + delete(cache, serial) + registry.RemoveDevice(ctx, cached) + } + } + + return nil +} + +// ParseDevices parses the json formatted device list from ffx. +func ParseDevices(ctx context.Context, stdout string) (map[string]bind.Status, error) { + // + // Example "ffx target list -f json" output from Fuchsia device list on + // emulator: + // + // [{"nodename":"fuchsia-5254-0063-5e7a","rcs_state":"N","serial":"", + // "target_type":"Unknown","target_state":"Product", + // "addresses":["fe80::fe49:e0eb:776f:a2c5%qemu"]}] + // + + var targetJSON []map[string]interface{} + if err := json.Unmarshal([]byte(stdout), &targetJSON); err != nil { + return nil, err + } + + if len(targetJSON) == 0 { + return nil, ErrNoDeviceList + } + + devices := make(map[string]bind.Status, len(targetJSON)) + for _, targetMap := range targetJSON { + if targetName, ok := targetMap["nodename"].(string); ok { + if rcsStatus, ok := targetMap["rcs_state"].(string); ok { + if strings.ToUpper(rcsStatus) == "Y" { + devices[targetName] = bind.Online + } else { + log.I(ctx, "Skipping unreachable Fuchsia device: %v", targetName) + } + } else { + return nil, ErrInvalidDeviceList + } + } else { + return nil, ErrInvalidDeviceList + } + } + + return devices, nil +} diff --git a/core/os/fuchsia/ffx/device_test.go b/core/os/fuchsia/ffx/device_test.go new file mode 100644 index 0000000000..1aecd500f7 --- /dev/null +++ b/core/os/fuchsia/ffx/device_test.go @@ -0,0 +1,67 @@ +// Copyright (C) 2021 Google Inc. +// +// 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. + +package ffx_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/gapid/core/assert" + "github.com/google/gapid/core/log" + "github.com/google/gapid/core/os/fuchsia/ffx" +) + +const deviceJSONPattern = `{ + "nodename": "%s", + "rcs_state": "%s", + "serial": "", + "target_type": "Unknown", + "target_state": "Product", + "addresses":["%s"] +} +` + +func TestParseDevices(t_ *testing.T) { + ctx := log.Testing(t_) + + // Concatenate IP addresses and device names together as a facsimile of the real stdout from ffx + // with 3 available devices. + ipAddrs := []string{"fe80::5054:ff:fe63:5e7a%1", "fe80::5054:ff:fe63:5e7a%1", "fe80::5054:ff:fe63:5e7a%1"} + deviceNames := []string{"fuchsia-5254-0063-5e7a", "fuchsia-5254-0063-5e7b", "fuchsia-5254-0063-5e7c"} + + var deviceJSON []string + for i := range ipAddrs { + deviceJSON = append(deviceJSON, fmt.Sprintf(deviceJSONPattern, deviceNames[i], "Y", ipAddrs[i])) + } + + deviceMap, err := ffx.ParseDevices(ctx, "["+strings.Join(deviceJSON, ",")+"]") + if assert.For(ctx, "Valid devices").ThatError(err).Succeeded() { + devicesFound := 0 + for deviceName := range deviceMap { + for _, currDeviceName := range deviceNames { + if currDeviceName == deviceName { + devicesFound++ + break + } + } + } + assert.For(ctx, "Valid devices").ThatInteger(devicesFound).Equals(len(ipAddrs)) + } + + deviceMap, err = ffx.ParseDevices(ctx, "\nFile not found.\n\n") + assert.For(ctx, "Garbage input").ThatError(err).Failed() + assert.For(ctx, "Garbage input").ThatSlice(deviceMap).IsEmpty() +} diff --git a/core/os/process/client.go b/core/os/process/client.go index e76287a360..7388e3c455 100644 --- a/core/os/process/client.go +++ b/core/os/process/client.go @@ -51,7 +51,7 @@ type PortWatcher struct { fragment string done bool portFile string - device bind.Device + device bind.Desktop } func (w *PortWatcher) getPortFromFile(ctx context.Context) (string, bool) { @@ -144,7 +144,7 @@ type StartOptions struct { IgnorePort bool // Device, which device should this be started on - Device bind.Device + Device bind.Desktop } // StartOnDevice runs the application on the given remote device, diff --git a/core/os/shell/BUILD.bazel b/core/os/shell/BUILD.bazel index 8ca3d4d688..8211bc0c74 100644 --- a/core/os/shell/BUILD.bazel +++ b/core/os/shell/BUILD.bazel @@ -40,20 +40,11 @@ go_test( "command_test.go", "env_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", - ] + select({ - "@io_bazel_rules_go//go/platform:darwin": [ - "//core/event/task:go_default_library", - "//core/fault:go_default_library", - "//core/log:go_default_library", - ], - "@io_bazel_rules_go//go/platform:linux": [ - "//core/event/task:go_default_library", - "//core/fault:go_default_library", - "//core/log:go_default_library", - ], - "//conditions:default": [], - }), + "//core/event/task:go_default_library", + "//core/fault:go_default_library", + "//core/log:go_default_library", + ], ) diff --git a/core/os/shell/command_example_test.go b/core/os/shell/command_example_test.go index 34a9a67c4f..eec526f90e 100644 --- a/core/os/shell/command_example_test.go +++ b/core/os/shell/command_example_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux darwin +//go:build (linux && !android) || (darwin && !ios) package shell_test diff --git a/core/os/shell/command_test.go b/core/os/shell/command_test.go index 7d8c3fc0ed..647bb12975 100644 --- a/core/os/shell/command_test.go +++ b/core/os/shell/command_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux darwin +//go:build (linux && !android) || (darwin && !ios) package shell_test diff --git a/core/os/shell/stub/BUILD.bazel b/core/os/shell/stub/BUILD.bazel index d0b204515c..e069756fa6 100644 --- a/core/os/shell/stub/BUILD.bazel +++ b/core/os/shell/stub/BUILD.bazel @@ -37,23 +37,10 @@ go_test( name = "go_default_test", size = "small", srcs = ["stub_example_test.go"], - embed = [":go_default_library"], - deps = select({ - "@io_bazel_rules_go//go/platform:darwin": [ - "//core/event/task:go_default_library", - "//core/log:go_default_library", - "//core/os/shell:go_default_library", - ], - "@io_bazel_rules_go//go/platform:linux": [ - "//core/event/task:go_default_library", - "//core/log:go_default_library", - "//core/os/shell:go_default_library", - ], - "@io_bazel_rules_go//go/platform:windows": [ - "//core/event/task:go_default_library", - "//core/log:go_default_library", - "//core/os/shell:go_default_library", - ], - "//conditions:default": [], - }), + deps = [ + ":go_default_library", + "//core/event/task:go_default_library", + "//core/log:go_default_library", + "//core/os/shell:go_default_library", + ], ) diff --git a/core/os/shell/stub/stub_example_test.go b/core/os/shell/stub/stub_example_test.go index 6a9e50f6bd..187dd028a1 100644 --- a/core/os/shell/stub/stub_example_test.go +++ b/core/os/shell/stub/stub_example_test.go @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// +build linux darwin windows - package stub_test import ( diff --git a/core/stream/BUILD.bazel b/core/stream/BUILD.bazel index 137bedbe10..9e8be08adb 100644 --- a/core/stream/BUILD.bazel +++ b/core/stream/BUILD.bazel @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") +load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") go_library( name = "go_default_library", @@ -67,8 +68,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["convert_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/stream/fmts:go_default_library", ], diff --git a/core/stream/convert.go b/core/stream/convert.go index 7bebdd76ad..d38f4dc356 100644 --- a/core/stream/convert.go +++ b/core/stream/convert.go @@ -87,13 +87,21 @@ func Convert(dst, src *Format, data []byte) ([]byte, error) { // Some components can be implicitly added (alpha, Y, Z, W). mappings = resolveImplicitMappings(count, mappings, src, data) - // Do the conversion work. + // Calculate min/max if floats + min, max := float64(math.MaxFloat64), -float64(math.MaxFloat64) for _, m := range mappings { if m.src.component == nil { return nil, fmt.Errorf("Channel %v not found in source format: %v", m.dst.component.Channel, src) } - if err := m.conv(count); err != nil { + if m.dst.component.DataType.IsInteger() && m.src.component.DataType.IsFloat() && !m.dst.component.DataType.Signed { + readMinMax(count, m.src, &min, &max) + } + } + + // Do the conversion work. + for _, m := range mappings { + if err := m.conv(count, min, max); err != nil { return nil, err } @@ -110,7 +118,7 @@ func resolveImplicitMappings(count int, mappings []mapping, srcFmt *Format, srcD srcChannels := srcFmt.Channels() switch m.dst.component.Channel { case Channel_Alpha: - if srcChannels.ContainsColor() || srcChannels.ContainsDepth() { + if srcChannels.ContainsColor() || srcChannels.ContainsDepth() || srcChannels.ContainsStencil() { m.src = buf1Norm } case Channel_W: @@ -128,6 +136,9 @@ func resolveImplicitMappings(count int, mappings []mapping, srcFmt *Format, srcD } else if c, _ := srcFmt.Component(Channel_Depth); c != nil { // Convert depth to RGB. m.src = buf{srcData, c, srcFmt.BitOffsets()[c], uint32(srcFmt.Stride()) * 8} + } else if c, _ := srcFmt.Component(Channel_Stencil); c != nil { + // Convert stencil to RGB. + m.src = buf{srcData, c, srcFmt.BitOffsets()[c], uint32(srcFmt.Stride()) * 8} } else if srcChannels.ContainsColor() { m.src = buf0 } @@ -155,7 +166,7 @@ var ( buf1Norm = buf{[]byte{1}, &Component{DataType: &U1, Sampling: LinearNormalized}, 0, 0} ) -func (m *mapping) conv(count int) error { +func (m *mapping) conv(count int, min, max float64) error { d, s := m.dst.component, m.src.component if d.GetSampling().GetCurve() != s.GetSampling().GetCurve() { return fmt.Errorf("Cannot convert curve from %v to %v", s.GetSampling().GetCurve(), d.GetSampling().GetCurve()) @@ -172,12 +183,13 @@ func (m *mapping) conv(count int) error { return intCast(count, m.dst, m.src) } // Source is normalized - if d.DataType.Signed == s.DataType.Signed { + if d.DataType.Signed == s.DataType.Signed || s.DataType.Signed { if d.DataType.GetInteger().Bits > s.DataType.GetInteger().Bits { return intExpand(count, m.dst, m.src) } return intCollapse(count, m.dst, m.src) } + return fmt.Errorf("Cannot convert from Int %v to Int %v", s, d) case dstIsFloat && srcIsInt: if s.DataType.Signed { return stof(count, m.dst, m.src) @@ -187,20 +199,21 @@ func (m *mapping) conv(count int) error { if d.DataType.Signed { return ftos(count, m.dst, m.src) } - return ftou(count, m.dst, m.src) + return ftou(count, m.dst, m.src, min, max) + default: + return fmt.Errorf("Cannot convert from Unknown %v to Unknown %v", s, d) } - return fmt.Errorf("Cannot convert from %v to %v", s, d) } // straight up copy. func clone(count int, dst, src buf) error { - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} bits := dst.component.DataType.Bits() for i := 0; i < count; i++ { - ds.Write(ss.Read(bits), bits) - ds.WritePos += dst.stride - bits - ss.ReadPos += src.stride - bits + destStream.Write(sourceStream.Read(bits), bits) + destStream.WritePos += dst.stride - bits + sourceStream.ReadPos += src.stride - bits } return nil } @@ -211,17 +224,17 @@ func intCast(count int, dst, src buf) error { dstBitsIncSign, srcBitsIncSign := dstTy.Bits(), srcTy.Bits() srcBitsExcSign := srcTy.GetInteger().Bits signed := dstTy.Signed - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} srcSignEx := ^(uint64(1<> shift - ds.Write(uint64(v), dstBitsExcSign) - if signed { - ds.Write(ss.Read(1), 1) // Copy sign + destStream.Write(uint64(v), dstBitsExcSign) + if dstTy.Signed { + destStream.Write(sourceStream.Read(1), 1) // Copy sign } - ds.WritePos += dst.stride - dstBitsIncSign - ss.ReadPos += src.stride - srcBitsIncSign + destStream.WritePos += dst.stride - dstBitsIncSign + sourceStream.ReadPos += src.stride - srcBitsIncSign } return nil } @@ -267,23 +288,59 @@ func intExpand(count int, dst, src buf) error { // int to smaller bit precision (drops LSBs) func intCollapse(count int, dst, src buf) error { dstTy, srcTy := dst.component.DataType, src.component.DataType - if dstTy.Signed != srcTy.Signed { - return fmt.Errorf("intCollapse cannot perform signed conversion") + if !srcTy.Signed && dstTy.Signed { + return fmt.Errorf("intCollapse cannot perform unsigned-to-signed conversion") } dstBitsIncSign, srcBitsIncSign := dstTy.Bits(), srcTy.Bits() dstBitsExcSign, srcBitsExcSign := dstTy.GetInteger().Bits, srcTy.GetInteger().Bits shift := srcBitsIncSign - dstBitsIncSign - signed := dstTy.Signed - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + maxPossibleValue := math.Pow(2, float64(srcBitsExcSign)) - 1 + maxWithSign := math.Pow(2, float64(srcBitsIncSign)) - 1 + min, max := maxPossibleValue, float64(0) + if src.component.Channel == Channel_Depth { + for i := 0; i < count; i++ { + f := float64(sourceStream.Read(srcBitsExcSign)) / maxPossibleValue + if f < min { + min = f + } + if f > max { + max = f + } + sourceStream.ReadPos += src.stride - srcBitsIncSign + } + sourceStream.ReadPos = src.offset + } for i := 0; i < count; i++ { - v := ss.Read(srcBitsExcSign) >> shift - ds.Write(uint64(v), dstBitsExcSign) - if signed { - ds.Write(ss.Read(1), 1) // Copy sign + v := uint64(0) + + if srcTy.Signed && !dstTy.Signed { + v = sourceStream.Read(srcBitsIncSign) + v = uint64(float64(v) + ((maxWithSign + 1) / 2)) + } else { + v = sourceStream.Read(srcBitsExcSign) + + if src.component.Channel == Channel_Depth { + f := float64(v) / maxPossibleValue + + if max != min { + f = (f - min) * (1 / (max - min)) + } else { + f = 0 + } + + v = uint64(f * maxPossibleValue) + } + } + + v >>= shift + destStream.Write(uint64(v), dstBitsExcSign) + if dstTy.Signed { + destStream.Write(sourceStream.Read(1), 1) // Copy sign } - ds.WritePos += dst.stride - dstBitsIncSign - ss.ReadPos += src.stride - srcBitsIncSign + destStream.WritePos += dst.stride - dstBitsIncSign + sourceStream.ReadPos += src.stride - srcBitsIncSign } return nil } @@ -298,24 +355,24 @@ func utof(count int, dst, src buf) error { norm := src.component.IsNormalized() dstBitsIncSign := dstTy.Bits() srcBits := srcTy.Bits() - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} scale := 1.0 / float64((uint(1)< *max { + *max = float64(f) + } + } + sourceStream.ReadPos += src.stride - 16 + } + case srcIsF32: + for i := 0; i < count; i++ { + f := math.Float32frombits(uint32(sourceStream.Read(32))) + if !math.IsInf(float64(f), 0) { + if float64(f) < *min { + *min = float64(f) + } + if float64(f) > *max { + *max = float64(f) + } + } + sourceStream.ReadPos += src.stride - 32 + } + case srcIsF64: + for i := 0; i < count; i++ { + f := math.Float64frombits(sourceStream.Read(64)) + if !math.IsInf(f, 0) { + if f < *min { + *min = f + } + if f > *max { + *max = f + } + } + sourceStream.ReadPos += src.stride - 64 + } + default: + srcExpBits, srcManBits := srcTy.GetFloat().ExponentBits, srcTy.GetFloat().MantissaBits + srcBits := srcTy.Bits() + for i := 0; i < count; i++ { + f := float64(f64.FromBits(sourceStream.Read(srcBits), srcExpBits, srcManBits)) + if !math.IsInf(f, 0) { + if f < *min { + *min = f + } + if f > *max { + *max = f + } + } + sourceStream.ReadPos += src.stride - srcBits + } + } + return nil +} + +func remapFloat(f, min, max float64, c Channel) float64 { + if c == Channel_Depth { + if max != min { + return (f - min) * (1 / (max - min)) + } else { + return 0 + } + } else if min < 0 && max > 0 { + if f <= 0 { + return (f - min) * (0.5 / -min) + } else { + return f*(0.5/max) + 0.5 + } + } else if max <= 0 { + if min == 0 { + return 0 + } else { + return (f - min) / -min + } + } else if max > 1 { + return f / max + } + + return f +} + // float to unsigned int -func ftou(count int, dst, src buf) error { +func ftou(count int, dst, src buf, min, max float64) error { dstTy, srcTy := dst.component.DataType, src.component.DataType srcIsF16, srcIsF32, srcIsF64 := srcTy.Is(F16), srcTy.Is(F32), srcTy.Is(F64) srcExpBits, srcManBits := srcTy.GetFloat().ExponentBits, srcTy.GetFloat().MantissaBits norm := dst.component.IsNormalized() dstBits, srcBits := dstTy.Bits(), srcTy.Bits() dstMask := (1 << dstBits) - 1 - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} switch { case srcIsF16: scale := float32(dstMask) for i := 0; i < count; i++ { - f := f16.Number(ss.Read(16)).Float32() + f := f16.Number(sourceStream.Read(16)).Float32() if norm { + f = float32(remapFloat(float64(f), min, max, src.component.Channel)) f *= scale } - writeUintClamped(&ds, uint64(f), dstBits) - ds.WritePos += dst.stride - dstBits - ss.ReadPos += src.stride - 16 + writeUintClamped(&destStream, uint64(f), dstBits) + destStream.WritePos += dst.stride - dstBits + sourceStream.ReadPos += src.stride - 16 } case srcIsF32: scale := float32(dstMask) for i := 0; i < count; i++ { - f := math.Float32frombits(uint32(ss.Read(32))) + f := math.Float32frombits(uint32(sourceStream.Read(32))) if norm { + f = float32(remapFloat(float64(f), min, max, src.component.Channel)) f *= scale } - writeUintClamped(&ds, uint64(f), dstBits) - ds.WritePos += dst.stride - dstBits - ss.ReadPos += src.stride - 32 + writeUintClamped(&destStream, uint64(f), dstBits) + destStream.WritePos += dst.stride - dstBits + sourceStream.ReadPos += src.stride - 32 } case srcIsF64: scale := float64(dstMask) for i := 0; i < count; i++ { - f := math.Float64frombits(ss.Read(64)) + f := math.Float64frombits(sourceStream.Read(64)) if norm { + f = remapFloat(f, min, max, src.component.Channel) f *= scale } - writeUintClamped(&ds, uint64(f), dstBits) - ds.WritePos += dst.stride - dstBits - ss.ReadPos += src.stride - 64 + writeUintClamped(&destStream, uint64(f), dstBits) + destStream.WritePos += dst.stride - dstBits + sourceStream.ReadPos += src.stride - 64 } default: scale := float64(dstMask) for i := 0; i < count; i++ { - f := float64(f64.FromBits(ss.Read(srcBits), srcExpBits, srcManBits)) + f := float64(f64.FromBits(sourceStream.Read(srcBits), srcExpBits, srcManBits)) if norm { + f = remapFloat(f, min, max, src.component.Channel) f *= scale } - writeUintClamped(&ds, uint64(f), dstBits) - ds.WritePos += dst.stride - dstBits - ss.ReadPos += src.stride - srcBits + writeUintClamped(&destStream, uint64(f), dstBits) + destStream.WritePos += dst.stride - dstBits + sourceStream.ReadPos += src.stride - srcBits } } return nil @@ -432,48 +582,48 @@ func ftos(count int, dst, src buf) error { dstBitsIncSign, srcBits := dstTy.Bits(), srcTy.Bits() norm := dst.component.IsNormalized() mul := (1 << dstBitsIncSign) - 1 - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} switch { case srcIsF16: for i := 0; i < count; i++ { - f := f16.Number(ss.Read(16)).Float32() + f := f16.Number(sourceStream.Read(16)).Float32() if norm { f = (f * float32(mul) / 2) - 0.5 } - ds.Write(uint64(f32.Round(f)), dstBitsIncSign) - ds.WritePos += dst.stride - dstBitsIncSign - ss.ReadPos += src.stride - 16 + destStream.Write(uint64(f32.Round(f)), dstBitsIncSign) + destStream.WritePos += dst.stride - dstBitsIncSign + sourceStream.ReadPos += src.stride - 16 } case srcIsF32: for i := 0; i < count; i++ { - f := math.Float32frombits(uint32(ss.Read(32))) + f := math.Float32frombits(uint32(sourceStream.Read(32))) if norm { f = (f * float32(mul) / 2) - 0.5 } - ds.Write(uint64(f32.Round(f)), dstBitsIncSign) - ds.WritePos += dst.stride - dstBitsIncSign - ss.ReadPos += src.stride - 32 + destStream.Write(uint64(f32.Round(f)), dstBitsIncSign) + destStream.WritePos += dst.stride - dstBitsIncSign + sourceStream.ReadPos += src.stride - 32 } case srcIsF64: for i := 0; i < count; i++ { - f := math.Float64frombits(ss.Read(64)) + f := math.Float64frombits(sourceStream.Read(64)) if norm { f = (f * float64(mul) / 2) - 0.5 } - ds.Write(uint64(f64.Round(f)), dstBitsIncSign) - ds.WritePos += dst.stride - dstBitsIncSign - ss.ReadPos += src.stride - 64 + destStream.Write(uint64(f64.Round(f)), dstBitsIncSign) + destStream.WritePos += dst.stride - dstBitsIncSign + sourceStream.ReadPos += src.stride - 64 } default: for i := 0; i < count; i++ { - f := float64(f64.FromBits(ss.Read(srcBits), srcExpBits, srcManBits)) + f := float64(f64.FromBits(sourceStream.Read(srcBits), srcExpBits, srcManBits)) if norm { f = (f * float64(mul) / 2) - 0.5 } - ds.Write(uint64(f64.Round(f)), dstBitsIncSign) - ds.WritePos += dst.stride - dstBitsIncSign - ss.ReadPos += src.stride - srcBits + destStream.Write(uint64(f64.Round(f)), dstBitsIncSign) + destStream.WritePos += dst.stride - dstBitsIncSign + sourceStream.ReadPos += src.stride - srcBits } } return nil @@ -489,31 +639,31 @@ func ftof(count int, dst, src buf) error { if !(dstIsF16 || dstIsF32 || dstIsF64) { return fmt.Errorf("Cannot convert to %v", dstTy) } - ss := binary.BitStream{Data: src.bytes, ReadPos: src.offset} - ds := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} + sourceStream := binary.BitStream{Data: src.bytes, ReadPos: src.offset} + destStream := binary.BitStream{Data: dst.bytes, WritePos: dst.offset} for i := 0; i < count; i++ { var f float64 switch { case srcIsF16: - f = float64(f16.Number(ss.Read(16)).Float32()) + f = float64(f16.Number(sourceStream.Read(16)).Float32()) case srcIsF32: - f = float64(math.Float32frombits(uint32(ss.Read(32)))) + f = float64(math.Float32frombits(uint32(sourceStream.Read(32)))) case srcIsF64: - f = float64(math.Float64frombits(ss.Read(64))) + f = float64(math.Float64frombits(sourceStream.Read(64))) default: - f = float64(f64.FromBits(ss.Read(srcBits), srcExpBits, srcManBits)) + f = float64(f64.FromBits(sourceStream.Read(srcBits), srcExpBits, srcManBits)) } switch { case dstIsF16: - ds.Write(uint64(f16.From(float32(f))), 16) + destStream.Write(uint64(f16.From(float32(f))), 16) case dstIsF32: - ds.Write(uint64(math.Float32bits(float32(f))), 32) + destStream.Write(uint64(math.Float32bits(float32(f))), 32) case dstIsF64: - ds.Write(math.Float64bits(f), 64) + destStream.Write(math.Float64bits(f), 64) } - ds.WritePos += dst.stride - dstBits - ss.ReadPos += src.stride - srcBits + destStream.WritePos += dst.stride - dstBits + sourceStream.ReadPos += src.stride - srcBits } return nil diff --git a/core/stream/curve.go b/core/stream/curve.go index 99ed166091..afb9510327 100644 --- a/core/stream/curve.go +++ b/core/stream/curve.go @@ -37,7 +37,7 @@ func (m *mapping) transform(count int, f func(float64) float64) error { stride: 64, }, } - if err := tmp.conv(count); err != nil { + if err := tmp.conv(count, 0, 0); err != nil { return err } r := endian.Reader(bytes.NewReader(data), device.LittleEndian) diff --git a/core/stream/datatype.go b/core/stream/datatype.go index ca3da9463f..fd577333ba 100644 --- a/core/stream/datatype.go +++ b/core/stream/datatype.go @@ -69,9 +69,9 @@ var ( // F16 represents a 16-bit signed, floating-point number. F16 = DataType{Signed: true, Kind: &DataType_Float{&Float{ExponentBits: 5, MantissaBits: 10}}} // F32 represents a 32-bit signed, floating-point number. - F32 = DataType{Signed: true, Kind: &DataType_Float{&Float{ExponentBits: 7, MantissaBits: 24}}} + F32 = DataType{Signed: true, Kind: &DataType_Float{&Float{ExponentBits: 8, MantissaBits: 23}}} // F64 represents a 64-bit signed, floating-point number. - F64 = DataType{Signed: true, Kind: &DataType_Float{&Float{ExponentBits: 10, MantissaBits: 53}}} + F64 = DataType{Signed: true, Kind: &DataType_Float{&Float{ExponentBits: 11, MantissaBits: 52}}} // S16_16 represents a 16.16 bit signed, fixed-point number. S16_16 = DataType{Signed: true, Kind: &DataType_Fixed{&Fixed{IntegerBits: 15, FractionalBits: 16}}} ) diff --git a/core/stream/fmts/BUILD.bazel b/core/stream/fmts/BUILD.bazel index 1fc42a8660..03c6b73d6a 100644 --- a/core/stream/fmts/BUILD.bazel +++ b/core/stream/fmts/BUILD.bazel @@ -50,8 +50,8 @@ go_test( name = "go_default_test", size = "small", srcs = ["fmts_test.go"], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/stream:go_default_library", ], diff --git a/core/stream/fmts/abgr.go b/core/stream/fmts/abgr.go index c02a3641eb..b6691d21b1 100644 --- a/core/stream/fmts/abgr.go +++ b/core/stream/fmts/abgr.go @@ -57,6 +57,26 @@ var ( }}, } + ABGR_U4_NORM = &stream.Format{ + Components: []*stream.Component{{ + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Alpha, + }, { + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Blue, + }, { + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Green, + }, { + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Red, + }}, + } + ABGR_U8_NORM = &stream.Format{ Components: []*stream.Component{{ DataType: &stream.U8, @@ -117,6 +137,26 @@ var ( }}, } + ABGR_U1U5U5U5_NORM = &stream.Format{ + Components: []*stream.Component{{ + DataType: &stream.U1, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Alpha, + }, { + DataType: &stream.U5, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Blue, + }, { + DataType: &stream.U5, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Green, + }, { + DataType: &stream.U5, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Red, + }}, + } + ABGR_U2U10U10U10 = &stream.Format{ Components: []*stream.Component{{ DataType: &stream.U2, diff --git a/core/stream/fmts/argb.go b/core/stream/fmts/argb.go index 0b3d9335e7..bb83e07b92 100644 --- a/core/stream/fmts/argb.go +++ b/core/stream/fmts/argb.go @@ -17,6 +17,26 @@ package fmts import "github.com/google/gapid/core/stream" var ( + ARGB_U4_NORM = &stream.Format{ + Components: []*stream.Component{{ + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Alpha, + }, { + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Red, + }, { + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Green, + }, { + DataType: &stream.U4, + Sampling: stream.LinearNormalized, + Channel: stream.Channel_Blue, + }}, + } + ARGB_U1U5U5U5_NORM = &stream.Format{ Components: []*stream.Component{{ DataType: &stream.U1, diff --git a/core/stream/fmts/fmts_test.go b/core/stream/fmts/fmts_test.go index a5c3daa305..d2ffdeb33d 100644 --- a/core/stream/fmts/fmts_test.go +++ b/core/stream/fmts/fmts_test.go @@ -30,13 +30,16 @@ func TestFormatNames(t *testing.T) { for n, f := range map[string]*stream.Format{ "ABGR_U8": fmts.ABGR_U8, "ABGR_S8": fmts.ABGR_S8, + "ABGR_U4_NORM": fmts.ABGR_U4_NORM, "ABGR_U8_NORM": fmts.ABGR_U8_NORM, "ABGR_S8_NORM": fmts.ABGR_S8_NORM, "ABGR_NU8N_sRGBU8N_sRGBU8N_sRGBU8": fmts.ABGR_NU8N_sRGBU8N_sRGBU8N_sRGBU8, + "ABGR_U1U5U5U5_NORM": fmts.ABGR_U1U5U5U5_NORM, "ABGR_U2U10U10U10": fmts.ABGR_U2U10U10U10, "ABGR_S2S10S10S10": fmts.ABGR_S2S10S10S10, "ABGR_U2U10U10U10_NORM": fmts.ABGR_U2U10U10U10_NORM, "ABGR_S2S10S10S10_NORM": fmts.ABGR_S2S10S10S10_NORM, + "ARGB_U4_NORM": fmts.ARGB_U4_NORM, "ARGB_U1U5U5U5_NORM": fmts.ARGB_U1U5U5U5_NORM, "ARGB_U2U10U10U10": fmts.ARGB_U2U10U10U10, "ARGB_S2S10S10S10": fmts.ARGB_S2S10S10S10, diff --git a/core/text/BUILD.bazel b/core/text/BUILD.bazel index 8e7bab31c0..637ee74fc4 100644 --- a/core/text/BUILD.bazel +++ b/core/text/BUILD.bazel @@ -37,8 +37,8 @@ go_test( "split_args_test.go", "writer_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/fault:go_default_library", "//core/log:go_default_library", diff --git a/core/text/parse/BUILD.bazel b/core/text/parse/BUILD.bazel index 4aa66c8bf6..0c45d9219d 100644 --- a/core/text/parse/BUILD.bazel +++ b/core/text/parse/BUILD.bazel @@ -39,8 +39,8 @@ go_test( "parser_test.go", "reader_test.go", ], - embed = [":go_default_library"], deps = [ + ":go_default_library", "//core/assert:go_default_library", "//core/fault:go_default_library", "//core/log:go_default_library", diff --git a/core/text/parse/parser.go b/core/text/parse/parser.go index b36b321874..871308ff26 100644 --- a/core/text/parse/parser.go +++ b/core/text/parse/parser.go @@ -125,9 +125,13 @@ func (p *Parser) ParseBranch(b *cst.Branch, do BranchParser) { // the newly insterted branch. // // Extend will transform: -// n.parent ──> n +// +// n.parent ──> n +// // to: -// n.parent ──> b ──> n +// +// n.parent ──> b ──> n +// // where b is passed as the second argument to do(). func (p *Parser) Extend(n cst.Node, do BranchParser) { if n == nil { diff --git a/core/vulkan/cc/include/ggp_c/BUILD.bazel b/core/vulkan/cc/include/ggp_c/BUILD.bazel deleted file mode 100644 index 30560b379a..0000000000 --- a/core/vulkan/cc/include/ggp_c/BUILD.bazel +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright (C) 2019 Google Inc. -# -# 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. - -cc_library( - name = "vulkan_ggp_dummy", - hdrs = glob(["*.h"]), - include_prefix = "ggp_c", - visibility = ["//visibility:public"], -) diff --git a/core/vulkan/cc/include/ggp_c/vulkan_types.h b/core/vulkan/cc/include/ggp_c/vulkan_types.h deleted file mode 100644 index 85159c02b2..0000000000 --- a/core/vulkan/cc/include/ggp_c/vulkan_types.h +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (C) 2019 Google Inc. -// -// 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. - -#ifndef DUMMY_VULKAN_TYPES_H__ -#define DUMMY_VULKAN_TYPES_H__ - -#include - -typedef uint32_t GgpStreamDescriptor; -typedef uint64_t GgpFrameToken; - -#endif // DUMMY_VULKAN_TYPES_H__ \ No newline at end of file diff --git a/core/vulkan/layer_helpers/vulkan_layer_helpers.h b/core/vulkan/layer_helpers/vulkan_layer_helpers.h index bb6630fc6c..6a61464757 100644 --- a/core/vulkan/layer_helpers/vulkan_layer_helpers.h +++ b/core/vulkan/layer_helpers/vulkan_layer_helpers.h @@ -154,4 +154,4 @@ struct Context { }; } // namespace layer_helpers -#endif // VULKAN_LAYER_HELPES_H__ \ No newline at end of file +#endif // VULKAN_LAYER_HELPERS_H__ diff --git a/core/vulkan/loader/loader.go b/core/vulkan/loader/loader.go index 2037fdafb7..98b348ed92 100644 --- a/core/vulkan/loader/loader.go +++ b/core/vulkan/loader/loader.go @@ -30,9 +30,15 @@ import ( "github.com/google/gapid/core/os/shell" ) +const ( + // Since Android NDK r21, the VK_LAYER_KHRONOS_validation meta layer + // is available on both desktop and Android. + VulkanValidationLayer = "VK_LAYER_KHRONOS_validation" +) + // setupHelper describes setting up the files on the device type setupHelper struct { - device bind.Device + device bind.Desktop abi *device.ABI } @@ -61,7 +67,7 @@ func (r *setupHelper) finalizeJSON(ctx context.Context, jsonName string, content // SetupLayers sets up the environment so that the correct layers are enabled // for the application. -func SetupLayers(ctx context.Context, layers []string, skipMissingLayers bool, d bind.Device, abi *device.ABI, env *shell.Env) (app.Cleanup, error) { +func SetupLayers(ctx context.Context, layers []string, skipMissingLayers bool, d bind.Desktop, abi *device.ABI, env *shell.Env) (app.Cleanup, error) { if len(layers) == 0 { return nil, nil } @@ -106,7 +112,7 @@ func SetupLayers(ctx context.Context, layers []string, skipMissingLayers bool, d // SetupTrace sets up the environment for tracing a local app. Returns a // clean-up function to be called after the trace completes, and a temporary // filename that can be used to find the port if stdout fails, or an error. -func SetupTrace(ctx context.Context, d bind.Device, abi *device.ABI, env *shell.Env) (app.Cleanup, string, error) { +func SetupTrace(ctx context.Context, d bind.Desktop, abi *device.ABI, env *shell.Env) (app.Cleanup, string, error) { setup := &setupHelper{d, abi} tempdir, cleanup, err := setup.makeTempDir(ctx) @@ -148,7 +154,7 @@ func SetupTrace(ctx context.Context, d bind.Device, abi *device.ABI, env *shell. // SetupReplay sets up the environment for a desktop. Returns a clean-up // function to be called after replay completes, or an error. -func SetupReplay(ctx context.Context, d bind.Device, abi *device.ABI, env *shell.Env) (app.Cleanup, error) { +func SetupReplay(ctx context.Context, d bind.Desktop, abi *device.ABI, env *shell.Env) (app.Cleanup, error) { setup := &setupHelper{d, abi} tempdir, cleanup, err := setup.makeTempDir(ctx) diff --git a/core/vulkan/perfetto_producer/perfetto_threadlocal_emitter.inc b/core/vulkan/perfetto_producer/perfetto_threadlocal_emitter.inc index 79a666a8cf..205a9900fc 100644 --- a/core/vulkan/perfetto_producer/perfetto_threadlocal_emitter.inc +++ b/core/vulkan/perfetto_producer/perfetto_threadlocal_emitter.inc @@ -337,12 +337,12 @@ void ThreadlocalEmitter::EmitVulkanMemoryUsageEvent( packet->set_timestamp(event->timestamp); if (is_reset) { packet->set_sequence_flags( - perfetto::protos::pbzero:: - TracePacket_SequenceFlags_SEQ_INCREMENTAL_STATE_CLEARED); + perfetto::protos::pbzero::TracePacket_SequenceFlags:: + SEQ_INCREMENTAL_STATE_CLEARED); } else { packet->set_sequence_flags( - perfetto::protos::pbzero:: - TracePacket_SequenceFlags_SEQ_NEEDS_INCREMENTAL_STATE); + perfetto::protos::pbzero::TracePacket_SequenceFlags:: + SEQ_NEEDS_INCREMENTAL_STATE); } perfetto::protos::pbzero::InternedData* interned_data = nullptr; diff --git a/core/vulkan/tools/BUILD.bazel b/core/vulkan/tools/BUILD.bazel index f137c8fa61..8d9d1b1f37 100644 --- a/core/vulkan/tools/BUILD.bazel +++ b/core/vulkan/tools/BUILD.bazel @@ -23,9 +23,9 @@ cc_library( copts = cc_copts() + select({ "//tools/build:linux": [ "-DVK_USE_PLATFORM_XCB_KHR", - "-DVK_USE_PLATFORM_GGP", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], @@ -36,6 +36,7 @@ cc_library( linkopts = select({ "//tools/build:linux": ["-lpthread"], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-lpthread"], # Android "//conditions:default": [ @@ -45,7 +46,6 @@ cc_library( }), visibility = ["//visibility:public"], deps = [ - "//core/vulkan/cc/include/ggp_c:vulkan_ggp_dummy", "@stb", "@vulkan-headers//:vulkan", ], @@ -61,6 +61,7 @@ cc_library( "-DVK_USE_PLATFORM_XCB_KHR", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], diff --git a/core/vulkan/vk_api_timing_layer/cc/BUILD.bazel b/core/vulkan/vk_api_timing_layer/cc/BUILD.bazel index 9bb25aa1f9..f72279a80e 100644 --- a/core/vulkan/vk_api_timing_layer/cc/BUILD.bazel +++ b/core/vulkan/vk_api_timing_layer/cc/BUILD.bazel @@ -47,9 +47,9 @@ cc_library( copts = cc_copts() + select({ "//tools/build:linux": [ "-DVK_USE_PLATFORM_XCB_KHR", - "-DVK_USE_PLATFORM_GGP", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], @@ -61,6 +61,7 @@ cc_library( linkopts = select({ "//tools/build:linux": ["-lpthread"], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-lpthread"], # Android "//conditions:default": [ @@ -72,7 +73,6 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//core/cc", - "//core/vulkan/cc/include/ggp_c:vulkan_ggp_dummy", "//core/vulkan/layer_helpers", "//core/vulkan/perfetto_producer", "@vulkan-headers//:vulkan", @@ -89,6 +89,7 @@ cc_library( "-DVK_USE_PLATFORM_XCB_KHR", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], diff --git a/core/vulkan/vk_api_timing_layer/cc/api_timing_android.exports b/core/vulkan/vk_api_timing_layer/cc/api_timing_android.exports index 6e529d3c25..2a44f5e1b0 100644 --- a/core/vulkan/vk_api_timing_layer/cc/api_timing_android.exports +++ b/core/vulkan/vk_api_timing_layer/cc/api_timing_android.exports @@ -4,4 +4,4 @@ vkEnumerateInstanceLayerProperties vkEnumerateInstanceExtensionProperties vkEnumerateDeviceLayerProperties vkEnumerateDeviceExtensionProperties -_layer_dummy_func__ \ No newline at end of file +_layer_keep_alive_func__ diff --git a/core/vulkan/vk_api_timing_layer/cc/api_timing_desktop.exports b/core/vulkan/vk_api_timing_layer/cc/api_timing_desktop.exports index 23f2475243..9673a122c0 100644 --- a/core/vulkan/vk_api_timing_layer/cc/api_timing_desktop.exports +++ b/core/vulkan/vk_api_timing_layer/cc/api_timing_desktop.exports @@ -1,3 +1,3 @@ CPUTimingGetDeviceProcAddr CPUTimingGetInstanceProcAddr -_layer_dummy_func__ \ No newline at end of file +_layer_keep_alive_func__ diff --git a/core/vulkan/vk_api_timing_layer/cc/layer_helpers.cpp b/core/vulkan/vk_api_timing_layer/cc/layer_helpers.cpp index 97f349dfc8..be3cc1ee2b 100644 --- a/core/vulkan/vk_api_timing_layer/cc/layer_helpers.cpp +++ b/core/vulkan/vk_api_timing_layer/cc/layer_helpers.cpp @@ -18,36 +18,35 @@ #include "core/cc/log.h" #include "core/cc/target.h" extern "C" { -__attribute__((constructor)) void _layer_dummy_func__(); +__attribute__((constructor)) void _layer_keep_alive_func__(); } #if (TARGET_OS == GAPID_OS_WINDOWS) || (TARGET_OS == GAPID_OS_OSX) -class dummy_struct {}; +class keep_alive_struct {}; #else #include #include #include -class dummy_struct { +class keep_alive_struct { public: - dummy_struct(); + keep_alive_struct(); }; -dummy_struct::dummy_struct() { - GAPID_ERROR("Loading dummy struct"); +keep_alive_struct::keep_alive_struct() { Dl_info info; - if (dladdr((void*)&_layer_dummy_func__, &info)) { + if (dladdr((void*)&_layer_keep_alive_func__, &info)) { dlopen(info.dli_fname, RTLD_NODELETE); } } #endif extern "C" { -// _layer_dummy_func__ is marked __attribute__((constructor)) +// _layer_keep_alive_func__ is marked __attribute__((constructor)) // this means on .so open, it will be called. Once that happens, -// we create a dummy struct, which on Android and Linux, -// Forces the layer to never be unloaded. There is some global -// state in perfetto producers that does not like being unloaded. -void _layer_dummy_func__() { - dummy_struct d; +// we create a struct, which on Android and Linux, forces the layer +// to never be unloaded. There is some global state in perfetto +// producers that does not like being unloaded. +void _layer_keep_alive_func__() { + keep_alive_struct d; (void)d; } } diff --git a/core/vulkan/vk_api_timing_layer/cc/timing_layer.tmpl b/core/vulkan/vk_api_timing_layer/cc/timing_layer.tmpl index 229150d92a..cb4712a038 100644 --- a/core/vulkan/vk_api_timing_layer/cc/timing_layer.tmpl +++ b/core/vulkan/vk_api_timing_layer/cc/timing_layer.tmpl @@ -18,49 +18,6 @@ {{Global "Vulkan.LayerName" "CPUTiming"}} {{Global "Vulkan.LayerDescription" "Vulkan API CPU Call Timing"}} -{{define "DEBUG_UTILS_FUNCTIONS"}} -vkSetDebugUtilsObjectNameEXT -vkSetDebugUtilsObjectTagEXT -vkQueueBeginDebugUtilsLabelEXT -vkQueueEndDebugUtilsLabelEXT -vkQueueInsertDebugUtilsLabelEXT -vkCmdBeginDebugUtilsLabelEXT -vkCmdEndDebugUtilsLabelEXT -vkCmdInsertDebugUtilsLabelEXT -vkCreateDebugUtilsMessengerEXT -vkDestroyDebugUtilsMessengerEXT -vkSubmitDebugUtilsMessageEXT -{{end}} - -{{define "IS_DEBUG_UTILS_FUNCTIONS"}} - {{$filters := Strings (Macro "DEBUG_UTILS_FUNCTIONS") | SplitEOL}} - {{range $f := $filters}} - {{if eq $.Name $f}}true{{end}} - {{end}} -{{end}} - -{{define "DEBUG_MARKER_FUNCTIONS"}} -vkDebugMarkerSetObjectTagEXT -vkDebugMarkerSetObjectNameEXT -vkCmdDebugMarkerBeginEXT -vkCmdDebugMarkerEndEXT -vkCmdDebugMarkerInsertEXT -{{end}} - -{{define "ALL_DEBUG_FUNCTIONS"}} - {{Macro "DEBUG_MARKER_FUNCTIONS"}} - {{Macro "DEBUG_UTILS_FUNCTIONS"}} -{{end}} - -{{define "IS_DEBUG_MARKER_FUNCTIONS"}} - {{$filters := Strings (Macro "DEBUG_MARKER_FUNCTIONS") | SplitEOL}} - {{range $f := $filters}} - {{if eq $.Name $f}}true{{end}} - {{end}} -{{end}} - -{{Global "Vulkan.ImplementedFunctions" (Strings (Macro "ALL_DEBUG_FUNCTIONS") | SplitEOL)}} - {{Include "../../../../gapis/api/vulkan/templates/vulkan_layer.tmpl"}} {{$ | Macro "layer_impl.cpp" | Reflow 4 | Write "layer_impl.cpp"}} @@ -70,11 +27,10 @@ vkCmdDebugMarkerInsertEXT {{Template "C++.Copyright"}} #include "core/vulkan/vk_api_timing_layer/cc/layer.h" #include "core/vulkan/vk_api_timing_layer/cc/tracing_helpers.h" -#include "core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.h" #include "core/cc/timer.h" #include -namespace api_timing { +namespace {{(Global "Vulkan.LayerNamespace")}} { struct timer { timer(const char* category, const char* name): @@ -87,13 +43,9 @@ struct timer { const char* cat; }; -static bool debug_utils_ext_supported = false; -static bool debug_marker_ext_supported = false; - {{range $c := AllCommands $}} {{$ind := Title (Macro "InitialIndirection" $c)}} {{if and (not (GetAnnotation $c "pfn")) (not (GetAnnotation $c "synthetic"))}} -{{if and (not (Macro "IS_DEBUG_UTILS_FUNCTIONS" $c)) (not (Macro "IS_DEBUG_MARKER_FUNCTIONS" $c)) (not (eq $c.Name "vkEnumerateDeviceExtensionProperties"))}} {{Template "BeginPlatformIfDef" $c}} {{Template "C++.BaseType" $c.Return.Type}} {{$c.Name}}(PFN_{{$c.Name}} next, {{Macro "C++.BaseCallParameters" $c | JoinWith ", "}}) { timer t("{{$ind}}", "{{$c.Name}}"); @@ -101,163 +53,8 @@ static bool debug_marker_ext_supported = false; } {{Template "EndPlatformIfDef" $c}} -{{else}} - -// Since this layer is declaring the debug extensions are implemented, we need to also implement -// all functions in the extension, but we need to be careful not to forward the function call if -// the extension was not implemented. -{{if and (Macro "IS_DEBUG_UTILS_FUNCTIONS" $c) (not (eq $c.Name "vkSetDebugUtilsObjectNameEXT"))}} -{{Template "BeginPlatformIfDef" $c}} -{{Template "C++.BaseType" $c.Return.Type}} {{$c.Name}}(PFN_{{$c.Name}} next, {{Macro "C++.BaseCallParameters" $c | JoinWith ", "}}) { - timer t("{{$ind}}", "{{$c.Name}}"); - -{{if not (IsVoid $c.Return.Type)}} - return debug_utils_ext_supported ? next({{Template "C++.CallArguments" $c}}) : VK_SUCCESS; -{{else}} - if (debug_utils_ext_supported) { - next({{Template "C++.CallArguments" $c}}); - } -{{end}} -} -{{Template "EndPlatformIfDef" $c}} -{{end}} -{{if and (Macro "IS_DEBUG_MARKER_FUNCTIONS" $c) (not (eq $c.Name "vkDebugMarkerSetObjectNameEXT"))}} -{{Template "BeginPlatformIfDef" $c}} -{{Template "C++.BaseType" $c.Return.Type}} {{$c.Name}}(PFN_{{$c.Name}} next, {{Macro "C++.BaseCallParameters" $c | JoinWith ", "}}) { - timer t("{{$ind}}", "{{$c.Name}}"); - -{{if not (IsVoid $c.Return.Type)}} - return debug_marker_ext_supported ? next({{Template "C++.CallArguments" $c}}) : VK_SUCCESS; -{{else}} - if (debug_marker_ext_supported) { - next({{Template "C++.CallArguments" $c}}); - } -{{end}} -} -{{Template "EndPlatformIfDef" $c}} -{{end}} - -{{end}} {{end}} {{end}} - -// Maps VkDebugReportObjectTypeEXT to VkObjectType. -VkObjectType getVkObjectType(VkDebugReportObjectTypeEXT vk_debug_report_object_type) { -#define CASE(OBJ) \ - case VK_DEBUG_REPORT_OBJECT_TYPE_##OBJ##_EXT: \ - return VK_OBJECT_TYPE_##OBJ - - switch (vk_debug_report_object_type) { - CASE(UNKNOWN); - CASE(INSTANCE); - CASE(PHYSICAL_DEVICE); - CASE(DEVICE); - CASE(QUEUE); - CASE(SEMAPHORE); - CASE(COMMAND_BUFFER); - CASE(FENCE); - CASE(DEVICE_MEMORY); - CASE(BUFFER); - CASE(IMAGE); - CASE(EVENT); - CASE(QUERY_POOL); - CASE(BUFFER_VIEW); - CASE(IMAGE_VIEW); - CASE(SHADER_MODULE); - CASE(PIPELINE_CACHE); - CASE(PIPELINE_LAYOUT); - CASE(RENDER_PASS); - CASE(PIPELINE); - CASE(DESCRIPTOR_SET_LAYOUT); - CASE(SAMPLER); - CASE(DESCRIPTOR_POOL); - CASE(DESCRIPTOR_SET); - CASE(FRAMEBUFFER); - CASE(COMMAND_POOL); - CASE(SURFACE_KHR); - CASE(SWAPCHAIN_KHR); - CASE(DEBUG_REPORT_CALLBACK_EXT); - CASE(DISPLAY_KHR); - CASE(DISPLAY_MODE_KHR); - CASE(OBJECT_TABLE_NVX); - CASE(INDIRECT_COMMANDS_LAYOUT_NVX); - CASE(VALIDATION_CACHE_EXT); - CASE(SAMPLER_YCBCR_CONVERSION); - CASE(DESCRIPTOR_UPDATE_TEMPLATE); - CASE(ACCELERATION_STRUCTURE_NV); - default: - return VK_OBJECT_TYPE_UNKNOWN; - } -#undef CASE -} - -VkResult vkSetDebugUtilsObjectNameEXT( - PFN_vkSetDebugUtilsObjectNameEXT next, - VkDevice device, - const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { - timer t("VkDevice", "vkSetDebugUtilsObjectNameEXT"); - - api_timing::VkApiEmit().EmitDebugUtilsObjectName( - reinterpret_cast(device), pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName); - - // Must not forward function call if the extension was not supported. - return debug_utils_ext_supported ? next(device, pNameInfo) : VK_SUCCESS; -} - - -VkResult vkDebugMarkerSetObjectNameEXT( - PFN_vkDebugMarkerSetObjectNameEXT next, - VkDevice device, - VkDebugMarkerObjectNameInfoEXT const* pNameInfo) { - timer t("VkDevice", "vkDebugMarkerSetObjectNameEXT"); - - // Convert object type to VkObjectType and emit the trace. - api_timing::VkApiEmit().EmitDebugUtilsObjectName( - reinterpret_cast(device), getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName); - - // Must not forward function call if the extension was not supported. - return debug_marker_ext_supported ? next(device, pNameInfo) : VK_SUCCESS; -} - -static const uint32_t NUM_EXTENSIONS = 2; -static const VkExtensionProperties new_device_extensions[] = { - {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, - {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, -}; - -// This layer needs to add VK_EXT_debug_utils and VK_EXT_debug_marker as a supported extension. -VkResult vkEnumerateDeviceExtensionProperties(PFN_vkEnumerateDeviceExtensionProperties next, VkPhysicalDevice physicalDevice, char const* pLayerName, uint32_t* pPropertyCount, VkExtensionProperties* pProperties) { - - timer t("VkPhysicalDevice", "vkEnumerateDeviceExtensionProperties"); - if (pProperties == NULL) { - VkResult res = next(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - (*pPropertyCount) += NUM_EXTENSIONS; - } - return res; - } - if (*pPropertyCount > 0) { - VkResult res = next(physicalDevice, pLayerName, pPropertyCount, pProperties); - if (res == VK_SUCCESS) { - uint32_t count = std::min(NUM_EXTENSIONS, *pPropertyCount); - for (uint32_t i = 0; i < *pPropertyCount; ++i) { - if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, pProperties[i].extensionName)) { - debug_utils_ext_supported = true; - } - if (!strcmp(VK_EXT_DEBUG_MARKER_EXTENSION_NAME, pProperties[i].extensionName)) { - debug_marker_ext_supported = true; - } - } - memcpy( - &pProperties[*pPropertyCount - count], - new_device_extensions, - count * sizeof(VkExtensionProperties)); - } - return res; - } - return VK_SUCCESS; -} - -} +} // end of {{(Global "Vulkan.LayerNamespace")}} {{end}} diff --git a/core/vulkan/vk_debug_marker_layer/apk/BUILD.bazel b/core/vulkan/vk_debug_marker_layer/apk/BUILD.bazel new file mode 100644 index 0000000000..dc19ff9e2e --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/apk/BUILD.bazel @@ -0,0 +1,21 @@ +# Copyright (C) 2019 Google Inc. +# +# 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 requ`ired 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. + +load("//tools/build:rules.bzl", "android_native") + +android_native( + name = "VkLayer_DebugMarker", + visibility = ["//visibility:public"], + deps = ["//core/vulkan/vk_debug_marker_layer/cc:libVkLayer_DebugMarker_android"], +) diff --git a/core/vulkan/vk_debug_marker_layer/cc/BUILD.bazel b/core/vulkan/vk_debug_marker_layer/cc/BUILD.bazel new file mode 100644 index 0000000000..292e9f4d6c --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/cc/BUILD.bazel @@ -0,0 +1,123 @@ +# Copyright (C) 2020 Google Inc. +# +# 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. + +load("//tools/build:rules.bzl", "android_dynamic_library", "api_template", "apic_template", "cc_copts", "cc_dynamic_library") + +api_template( + name = "debug_marker", + includes = [ + "//gapis/api/vulkan/templates:vulkan_layer", + ], + outputs = [ + "layer.h", + "layer.cpp", + "layer_impl.cpp", + ], + template = "debug_marker_layer.tmpl", +) + +apic_template( + name = "debug_marker_templated", + api = "//gapis/api/vulkan:api", + templates = [ + ":debug_marker", + ], +) + +cc_library( + name = "cc", + srcs = glob([ + "*.cpp", + "*.inc", + "*.h", + ]) + [ + ":debug_marker_templated", + ], + copts = cc_copts() + select({ + "//tools/build:linux": [ + "-DVK_USE_PLATFORM_XCB_KHR", + ], + "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], + "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], + # Android + "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], + }) + [ + "-fno-rtti", + "-fno-exceptions", + "-DNDEBUG", # always ndebug for perfetto + ], + linkopts = select({ + "//tools/build:linux": ["-lpthread"], + "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], + "//tools/build:windows": ["-lpthread"], + # Android + "//conditions:default": [ + "-ldl", + "-lm", + "-llog", + ], + }), + visibility = ["//visibility:public"], + deps = [ + "//core/cc", + "//core/vulkan/layer_helpers", + "//core/vulkan/perfetto_producer", + "@vulkan-headers//:vulkan", + ], +) + +cc_library( + name = "headers", + srcs = glob([ + "*.h", + ]), + copts = cc_copts() + select({ + "//tools/build:linux": [ + "-DVK_USE_PLATFORM_XCB_KHR", + ], + "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], + "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], + # Android + "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], + }), + visibility = ["//visibility:public"], + deps = [ + "@vulkan-headers//:vulkan", + ], +) + +cc_dynamic_library( + name = "libVkLayer_DebugMarker", + visibility = ["//visibility:public"], + exports = "debug_marker_desktop.exports", + deps = [":cc"], +) + +android_dynamic_library( + name = "libVkLayer_DebugMarker_android", + visibility = ["//visibility:public"], + exports = "debug_marker_android.exports", + deps = [":cc"], +) + +filegroup( + name = "json", + srcs = [ + "DebugMarkerLayer.json", + ], + visibility = ["//visibility:public"], +) diff --git a/core/vulkan/vk_debug_marker_layer/cc/DebugMarkerLayer.json b/core/vulkan/vk_debug_marker_layer/cc/DebugMarkerLayer.json new file mode 100644 index 0000000000..890145ea9e --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/cc/DebugMarkerLayer.json @@ -0,0 +1,15 @@ +{ + "file_format_version": "1.0.0", + "layer": { + "name": "DebugMarker", + "type": "GLOBAL", + "library_path": "", + "api_version": "1.0.5", + "implementation_version": "1", + "description": "Vulkan Debug Marker Producer", + "functions": { + "vkGetDeviceProcAddr": "VkApiGetDeviceProcAddr", + "vkGetInstanceProcAddr": "VkApiGetInstanceProcAddr" + } + } +} diff --git a/core/vulkan/vk_debug_marker_layer/cc/debug_marker_android.exports b/core/vulkan/vk_debug_marker_layer/cc/debug_marker_android.exports new file mode 100644 index 0000000000..f2a4f73d45 --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/cc/debug_marker_android.exports @@ -0,0 +1,7 @@ +DebugMarkerGetDeviceProcAddr +DebugMarkerGetInstanceProcAddr +vkEnumerateInstanceLayerProperties +vkEnumerateInstanceExtensionProperties +vkEnumerateDeviceLayerProperties +vkEnumerateDeviceExtensionProperties +_layer_keep_alive_func__ diff --git a/core/vulkan/vk_debug_marker_layer/cc/debug_marker_desktop.exports b/core/vulkan/vk_debug_marker_layer/cc/debug_marker_desktop.exports new file mode 100644 index 0000000000..e60d8beec1 --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/cc/debug_marker_desktop.exports @@ -0,0 +1,3 @@ +DebugMarkerGetDeviceProcAddr +DebugMarkerGetInstanceProcAddr +_layer_keep_alive_func__ diff --git a/core/vulkan/vk_debug_marker_layer/cc/debug_marker_layer.tmpl b/core/vulkan/vk_debug_marker_layer/cc/debug_marker_layer.tmpl new file mode 100644 index 0000000000..ece92be8a6 --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/cc/debug_marker_layer.tmpl @@ -0,0 +1,307 @@ +{{/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */}} + +{{Global "Vulkan.LayerNamespace" "vk_api"}} +{{Global "Vulkan.LayerName" "DebugMarker"}} +{{Global "Vulkan.LayerDescription" "Record Vulkan debug marker"}} + +{{define "DEBUG_UTILS_FUNCTIONS"}} +vkSetDebugUtilsObjectNameEXT +vkSetDebugUtilsObjectTagEXT +vkQueueBeginDebugUtilsLabelEXT +vkQueueEndDebugUtilsLabelEXT +vkQueueInsertDebugUtilsLabelEXT +vkCmdBeginDebugUtilsLabelEXT +vkCmdEndDebugUtilsLabelEXT +vkCmdInsertDebugUtilsLabelEXT +vkCreateDebugUtilsMessengerEXT +vkDestroyDebugUtilsMessengerEXT +vkSubmitDebugUtilsMessageEXT +{{end}} + +{{define "IS_DEBUG_UTILS_FUNCTIONS"}} + {{$filters := Strings (Macro "DEBUG_UTILS_FUNCTIONS") | SplitEOL}} + {{range $f := $filters}} + {{if eq $.Name $f}}true{{end}} + {{end}} +{{end}} + +{{define "DEBUG_MARKER_FUNCTIONS"}} +vkDebugMarkerSetObjectTagEXT +vkDebugMarkerSetObjectNameEXT +vkCmdDebugMarkerBeginEXT +vkCmdDebugMarkerEndEXT +vkCmdDebugMarkerInsertEXT +{{end}} + +{{define "OTHER_OVERRIDES"}} +vkEnumerateInstanceExtensionProperties +vkEnumerateDeviceExtensionProperties +{{end}} + + +{{define "ALL_DEBUG_FUNCTIONS"}} + {{Macro "DEBUG_MARKER_FUNCTIONS"}} + {{Macro "DEBUG_UTILS_FUNCTIONS"}} + {{Macro "OTHER_OVERRIDES"}} +{{end}} + +{{define "IS_DEBUG_MARKER_FUNCTIONS"}} + {{$filters := Strings (Macro "DEBUG_MARKER_FUNCTIONS") | SplitEOL}} + {{range $f := $filters}} + {{if eq $.Name $f}}true{{end}} + {{end}} +{{end}} + +{{Global "Vulkan.OverrideFunctions" (Strings (Macro "ALL_DEBUG_FUNCTIONS") | SplitEOL | TrimLeft " ")}} +{{Global "Vulkan.ImplementedFunctions" (Strings (Macro "ALL_DEBUG_FUNCTIONS") | SplitEOL | TrimLeft " ")}} + +{{Include "../../../../gapis/api/vulkan/templates/vulkan_layer.tmpl"}} + +{{$ | Macro "layer_impl.cpp" | Reflow 4 | Write "layer_impl.cpp"}} + +{{define "layer_impl.cpp"}} + +{{Template "C++.Copyright"}} +#include "core/vulkan/vk_debug_marker_layer/cc/layer.h" +#include "core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.h" +#include + +namespace {{(Global "Vulkan.LayerNamespace")}} { + +static bool debug_utils_ext_supported = false; +static bool debug_marker_ext_supported = false; + +// Since this layer is declaring the debug extensions are implemented, we need to also implement +// all functions in the extension, but we need to be careful not to forward the function call if +// the extension was not implemented. +{{range $c := AllCommands $}} + +{{if and (Macro "IS_DEBUG_UTILS_FUNCTIONS" $c) (not (eq $c.Name "vkSetDebugUtilsObjectNameEXT"))}} +{{Template "BeginPlatformIfDef" $c}} +{{Template "C++.BaseType" $c.Return.Type}} {{$c.Name}}(PFN_{{$c.Name}} next, {{Macro "C++.BaseCallParameters" $c | JoinWith ", "}}) { + +{{if not (IsVoid $c.Return.Type)}} + return (debug_utils_ext_supported && next != nullptr) ? next({{Template "C++.CallArguments" $c}}) : VK_SUCCESS; +{{else}} + if (next != nullptr) { + next({{Template "C++.CallArguments" $c}}); + } +{{end}} +} +{{Template "EndPlatformIfDef" $c}} +{{end}} + +{{if and (Macro "IS_DEBUG_MARKER_FUNCTIONS" $c) (not (eq $c.Name "vkDebugMarkerSetObjectNameEXT"))}} +{{Template "BeginPlatformIfDef" $c}} +{{Template "C++.BaseType" $c.Return.Type}} {{$c.Name}}(PFN_{{$c.Name}} next, {{Macro "C++.BaseCallParameters" $c | JoinWith ", "}}) { + +{{if not (IsVoid $c.Return.Type)}} + return (debug_marker_ext_supported && next != nullptr) ? next({{Template "C++.CallArguments" $c}}) : VK_SUCCESS; +{{else}} + if (next != nullptr) { + next({{Template "C++.CallArguments" $c}}); + } +{{end}} +} +{{Template "EndPlatformIfDef" $c}} +{{end}} + +{{end}} + + +// Maps VkDebugReportObjectTypeEXT to VkObjectType. +VkObjectType getVkObjectType(VkDebugReportObjectTypeEXT vk_debug_report_object_type) { +#define CASE(OBJ) \ + case VK_DEBUG_REPORT_OBJECT_TYPE_##OBJ##_EXT: \ + return VK_OBJECT_TYPE_##OBJ + + switch (vk_debug_report_object_type) { + CASE(UNKNOWN); + CASE(INSTANCE); + CASE(PHYSICAL_DEVICE); + CASE(DEVICE); + CASE(QUEUE); + CASE(SEMAPHORE); + CASE(COMMAND_BUFFER); + CASE(FENCE); + CASE(DEVICE_MEMORY); + CASE(BUFFER); + CASE(IMAGE); + CASE(EVENT); + CASE(QUERY_POOL); + CASE(BUFFER_VIEW); + CASE(IMAGE_VIEW); + CASE(SHADER_MODULE); + CASE(PIPELINE_CACHE); + CASE(PIPELINE_LAYOUT); + CASE(RENDER_PASS); + CASE(PIPELINE); + CASE(DESCRIPTOR_SET_LAYOUT); + CASE(SAMPLER); + CASE(DESCRIPTOR_POOL); + CASE(DESCRIPTOR_SET); + CASE(FRAMEBUFFER); + CASE(COMMAND_POOL); + CASE(SURFACE_KHR); + CASE(SWAPCHAIN_KHR); + CASE(DEBUG_REPORT_CALLBACK_EXT); + CASE(DISPLAY_KHR); + CASE(DISPLAY_MODE_KHR); + CASE(VALIDATION_CACHE_EXT); + CASE(SAMPLER_YCBCR_CONVERSION); + CASE(DESCRIPTOR_UPDATE_TEMPLATE); + CASE(ACCELERATION_STRUCTURE_NV); + CASE(CU_MODULE_NVX); + CASE(CU_FUNCTION_NVX); + CASE(ACCELERATION_STRUCTURE_KHR); + CASE(BUFFER_COLLECTION_FUCHSIA); + default: + return VK_OBJECT_TYPE_UNKNOWN; + } +#undef CASE +} + +VkResult vkSetDebugUtilsObjectNameEXT( + PFN_vkSetDebugUtilsObjectNameEXT next, + VkDevice device, + const VkDebugUtilsObjectNameInfoEXT* pNameInfo) { + vk_api::VkApiEmit().EmitDebugUtilsObjectName( + reinterpret_cast(device), pNameInfo->objectType, pNameInfo->objectHandle, pNameInfo->pObjectName); + + // Must not forward function call if the extension was not supported. + return debug_utils_ext_supported ? next(device, pNameInfo) : VK_SUCCESS; +} + +VkResult vkDebugMarkerSetObjectNameEXT( + PFN_vkDebugMarkerSetObjectNameEXT next, + VkDevice device, + VkDebugMarkerObjectNameInfoEXT const* pNameInfo) { + // Convert object type to VkObjectType and emit the trace. + vk_api::VkApiEmit().EmitDebugUtilsObjectName( + reinterpret_cast(device), getVkObjectType(pNameInfo->objectType), pNameInfo->object, pNameInfo->pObjectName); + + // Must not forward function call if the extension was not supported. + return debug_marker_ext_supported ? next(device, pNameInfo) : VK_SUCCESS; +} + +namespace { + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0])) + +const char* LAYER_NAME = "{{Global "Vulkan.LayerName"}}"; +const VkExtensionProperties INSTANCE_EXTENSIONS[] = { + {VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_SPEC_VERSION}, +}; +const uint32_t NUM_INSTANCE_EXTENSIONS = ARRAY_SIZE(INSTANCE_EXTENSIONS); +const VkExtensionProperties DEVICE_EXTENSIONS[] = { + {VK_EXT_DEBUG_MARKER_EXTENSION_NAME, VK_EXT_DEBUG_MARKER_SPEC_VERSION}, +}; +const uint32_t NUM_DEVICE_EXTENSIONS = ARRAY_SIZE(DEVICE_EXTENSIONS); + +#undef ARRAY_SIZE + +/** + * Enumerate extension properties for a specific layer. + * + * This should expose only the new extensions added by the layer. + */ +VkResult enumerateExtensionPropertiesForLayer( + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties, + uint32_t numExtensions, + const VkExtensionProperties extensions[]) { + if (pProperties == nullptr) { + *pPropertyCount = numExtensions; + return VK_SUCCESS; + } + uint32_t capacity = std::min(*pPropertyCount, numExtensions); + memcpy(pProperties, extensions, capacity * sizeof(VkExtensionProperties)); + if (*pPropertyCount < numExtensions) { + return VK_INCOMPLETE; + } else { + *pPropertyCount = numExtensions; + return VK_SUCCESS; + } +} +} // end of anonymous namespace + +// This layer needs to add VK_EXT_debug_utils and VK_EXT_debug_marker as a supported extension. + +VkResult vkEnumerateInstanceExtensionProperties( + PFN_vkEnumerateInstanceExtensionProperties next, + char const* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { + return enumerateExtensionPropertiesForLayer( + pPropertyCount, + pProperties, + NUM_INSTANCE_EXTENSIONS, + INSTANCE_EXTENSIONS); + } + return next(pLayerName, pPropertyCount, pProperties); +} + +VkResult vkEnumerateDeviceExtensionProperties( + PFN_vkEnumerateDeviceExtensionProperties next, + VkPhysicalDevice physicalDevice, + char const* pLayerName, + uint32_t* pPropertyCount, + VkExtensionProperties* pProperties) { + if (pLayerName != nullptr && strcmp(pLayerName, LAYER_NAME) == 0) { + return enumerateExtensionPropertiesForLayer( + pPropertyCount, + pProperties, + NUM_DEVICE_EXTENSIONS, + DEVICE_EXTENSIONS); + } + // Manually append device extension. This should not be necessary, but the Android vulkan + // loader does not expose extensions from implicit layer (b/143293104). + if (pProperties == nullptr) { + VkResult res = next(physicalDevice, pLayerName, pPropertyCount, pProperties); + if (res == VK_SUCCESS) { + (*pPropertyCount) += NUM_DEVICE_EXTENSIONS; + } + return res; + } + if (*pPropertyCount > 0) { + uint32_t requestedCount = *pPropertyCount; + VkResult res = next(physicalDevice, pLayerName, pPropertyCount, pProperties); + if (res == VK_SUCCESS) { + for (uint32_t i = 0; i < *pPropertyCount; ++i) { + if (!strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, pProperties[i].extensionName)) { + debug_utils_ext_supported = true; + } + if (!strcmp(VK_EXT_DEBUG_MARKER_EXTENSION_NAME, pProperties[i].extensionName)) { + debug_marker_ext_supported = true; + } + } + // *pPropertyCount is expected to be requestedCount - NUM_DEVICE_EXTENSIONS. + *pPropertyCount = std::min(*pPropertyCount + NUM_DEVICE_EXTENSIONS, requestedCount); + uint32_t count = std::min(NUM_DEVICE_EXTENSIONS, *pPropertyCount); + memcpy( + &pProperties[*pPropertyCount - count], + DEVICE_EXTENSIONS, + count * sizeof(VkExtensionProperties)); + } + return res; + } + return VK_SUCCESS; +} + +} // end of {{(Global "Vulkan.LayerNamespace")}} +{{end}} diff --git a/core/vulkan/vk_debug_marker_layer/cc/layer_helpers.cpp b/core/vulkan/vk_debug_marker_layer/cc/layer_helpers.cpp new file mode 100644 index 0000000000..39b91aaa7c --- /dev/null +++ b/core/vulkan/vk_debug_marker_layer/cc/layer_helpers.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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 "core/cc/log.h" +#include "core/cc/target.h" +extern "C" { +__attribute__((constructor)) void _layer_keep_alive_func__(); +} +#if (TARGET_OS == GAPID_OS_WINDOWS) || (TARGET_OS == GAPID_OS_OSX) +class keep_alive_struct {}; +#else +#include +#include +#include +class keep_alive_struct { + public: + keep_alive_struct(); +}; + +keep_alive_struct::keep_alive_struct() { + Dl_info info; + if (dladdr((void*)&_layer_keep_alive_func__, &info)) { + dlopen(info.dli_fname, RTLD_NODELETE); + } +} +#endif + +extern "C" { +// _layer_keep_alive_func__ is marked __attribute__((constructor)) +// this means on .so open, it will be called. Once that happens, +// we create a struct, which on Android and Linux, forces the layer +// to never be unloaded. There is some global state in perfetto +// producers that does not like being unloaded. +void _layer_keep_alive_func__() { + keep_alive_struct d; + (void)d; +} +} diff --git a/core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.cpp b/core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.cpp similarity index 81% rename from core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.cpp rename to core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.cpp index edefe3242a..ec2e4f80ce 100644 --- a/core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.cpp +++ b/core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.cpp @@ -15,5 +15,5 @@ * */ -#include "core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.h" -PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(api_timing::VkApiProducer); +#include "core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.h" +PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(vk_api::VkApiProducer); diff --git a/core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.h b/core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.h similarity index 90% rename from core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.h rename to core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.h index 6a1e3c114c..6fcad6fa6a 100644 --- a/core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.h +++ b/core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.h @@ -22,7 +22,7 @@ #include "core/vulkan/perfetto_producer/perfetto_data_source.h" #include "core/vulkan/perfetto_producer/perfetto_threadlocal_emitter.h" -namespace api_timing { +namespace vk_api { template class VkApiEmitter : ThreadlocalEmitterBase { @@ -81,12 +81,12 @@ struct VkApiTypeTraits { }; using VkApiProducer = VkApiEmitter; -auto const VkApiEmit = &api_timing::tracing::Emit; -} // namespace api_timing +auto const VkApiEmit = &vk_api::tracing::Emit; +} // namespace vk_api #define __INCLUDING_VK_API_EMITTER_INC__ -#include "core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.inc" +#include "core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.inc" #undef __INCLUDING_VK_API_EMITTER_INC__ -PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(api_timing::VkApiProducer); +PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(vk_api::VkApiProducer); #endif diff --git a/core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.inc b/core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.inc similarity index 99% rename from core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.inc rename to core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.inc index 50964be549..f8e32b52a8 100644 --- a/core/vulkan/vk_api_timing_layer/cc/vk_api_emitter.inc +++ b/core/vulkan/vk_debug_marker_layer/cc/vk_api_emitter.inc @@ -19,7 +19,7 @@ #include "perfetto/tracing/core/data_source_config.h" #include "perfetto/base/time.h" -namespace api_timing { +namespace vk_api { template VkApiEmitter::VkApiEmitter() { diff --git a/core/vulkan/vk_memory_tracker_layer/cc/BUILD.bazel b/core/vulkan/vk_memory_tracker_layer/cc/BUILD.bazel index de6522733b..8963d9469a 100644 --- a/core/vulkan/vk_memory_tracker_layer/cc/BUILD.bazel +++ b/core/vulkan/vk_memory_tracker_layer/cc/BUILD.bazel @@ -46,9 +46,9 @@ cc_library( copts = cc_copts() + select({ "//tools/build:linux": [ "-DVK_USE_PLATFORM_XCB_KHR", - "-DVK_USE_PLATFORM_GGP", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], @@ -60,6 +60,7 @@ cc_library( linkopts = select({ "//tools/build:linux": ["-lpthread"], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-lpthread"], # Android "//conditions:default": [ @@ -71,7 +72,6 @@ cc_library( visibility = ["//visibility:public"], deps = [ "//core/cc", - "//core/vulkan/cc/include/ggp_c:vulkan_ggp_dummy", "//core/vulkan/layer_helpers", "//core/vulkan/perfetto_producer", "@cityhash", @@ -89,6 +89,7 @@ cc_library( "-DVK_USE_PLATFORM_XCB_KHR", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], diff --git a/core/vulkan/vk_memory_tracker_layer/cc/layer_helpers.cpp b/core/vulkan/vk_memory_tracker_layer/cc/layer_helpers.cpp index 97f349dfc8..be3cc1ee2b 100644 --- a/core/vulkan/vk_memory_tracker_layer/cc/layer_helpers.cpp +++ b/core/vulkan/vk_memory_tracker_layer/cc/layer_helpers.cpp @@ -18,36 +18,35 @@ #include "core/cc/log.h" #include "core/cc/target.h" extern "C" { -__attribute__((constructor)) void _layer_dummy_func__(); +__attribute__((constructor)) void _layer_keep_alive_func__(); } #if (TARGET_OS == GAPID_OS_WINDOWS) || (TARGET_OS == GAPID_OS_OSX) -class dummy_struct {}; +class keep_alive_struct {}; #else #include #include #include -class dummy_struct { +class keep_alive_struct { public: - dummy_struct(); + keep_alive_struct(); }; -dummy_struct::dummy_struct() { - GAPID_ERROR("Loading dummy struct"); +keep_alive_struct::keep_alive_struct() { Dl_info info; - if (dladdr((void*)&_layer_dummy_func__, &info)) { + if (dladdr((void*)&_layer_keep_alive_func__, &info)) { dlopen(info.dli_fname, RTLD_NODELETE); } } #endif extern "C" { -// _layer_dummy_func__ is marked __attribute__((constructor)) +// _layer_keep_alive_func__ is marked __attribute__((constructor)) // this means on .so open, it will be called. Once that happens, -// we create a dummy struct, which on Android and Linux, -// Forces the layer to never be unloaded. There is some global -// state in perfetto producers that does not like being unloaded. -void _layer_dummy_func__() { - dummy_struct d; +// we create a struct, which on Android and Linux, forces the layer +// to never be unloaded. There is some global state in perfetto +// producers that does not like being unloaded. +void _layer_keep_alive_func__() { + keep_alive_struct d; (void)d; } } diff --git a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_android.exports b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_android.exports index 04df6cf7b4..42ae252e21 100644 --- a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_android.exports +++ b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_android.exports @@ -4,4 +4,4 @@ vkEnumerateInstanceLayerProperties vkEnumerateInstanceExtensionProperties vkEnumerateDeviceLayerProperties vkEnumerateDeviceExtensionProperties -_layer_dummy_func__ \ No newline at end of file +_layer_keep_alive_func__ diff --git a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_desktop.exports b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_desktop.exports index 51d77fa224..64f007cc26 100644 --- a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_desktop.exports +++ b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_desktop.exports @@ -1,3 +1,3 @@ MemoryTrackerGetDeviceProcAddr MemoryTrackerGetInstanceProcAddr -_layer_dummy_func__ \ No newline at end of file +_layer_keep_alive_func__ diff --git a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer.tmpl b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer.tmpl index d049eda65a..34f0d4ae04 100644 --- a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer.tmpl +++ b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer.tmpl @@ -65,7 +65,7 @@ vkDestroyImage {{if (and (HasPrefix $.Name "vk") (Contains "pAllocator" (Macro "C++.CallArguments" $)) )}}true{{end}} {{end}} -{{Global "Vulkan.OverrideFunctions" "dummy\n"}} +{{Global "Vulkan.OverrideFunctions" "placeholder\n"}} {{/* We need to override all the functions that use allocation callback or relate to gpu allocation */}} {{range $c := AllCommands $}} {{if (or (Macro "DoesGpuAllocation" $c) (Macro "DoesHaveAllocatorParam" $c) )}} @@ -75,7 +75,7 @@ vkDestroyImage {{end}} {{Global "Vulkan.OverrideFunctions" ((Global "Vulkan.OverrideFunctions") | SplitEOL)}} -{{Global "Vulkan.OtherUsedFunctions" "dummy" "vkGetPhysicalDeviceMemoryProperties" "vkGetBufferMemoryRequirements" "vkGetImageMemoryRequirements"}} +{{Global "Vulkan.OtherUsedFunctions" "placeholder" "vkGetPhysicalDeviceMemoryProperties" "vkGetBufferMemoryRequirements" "vkGetImageMemoryRequirements"}} {{$ | Macro "AllocationCallbackTracking" | Reflow 4 | Write "allocation_callbacks_impl.cpp"}} @@ -106,4 +106,3 @@ extern MemoryTracker memory_tracker_instance; {{end}} {{Include "../../../../gapis/api/vulkan/templates/vulkan_layer.tmpl"}} - diff --git a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer_impl.cpp b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer_impl.cpp index 8f61840078..ddf5f667e2 100644 --- a/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer_impl.cpp +++ b/core/vulkan/vk_memory_tracker_layer/cc/memory_tracker_layer_impl.cpp @@ -24,6 +24,7 @@ #include #endif +#include "core/cc/make_unique.h" #include "core/vulkan/vk_memory_tracker_layer/cc/tracing_helpers.h" #include "perfetto/base/time.h" @@ -40,11 +41,6 @@ MemoryTracker memory_tracker_instance; rwlock rwl_global_unique_handles; std::unordered_map global_unique_handles; -template -std::unique_ptr make_unique(Args&&... args) { - return std::unique_ptr(new T(std::forward(args)...)); -} - // --------------------------- UniqueHandleGenerator --------------------------- uint64_t UniqueHandleGenerator::Hash64(uint64_t handle, uint64_t counter) { @@ -338,8 +334,8 @@ VulkanMemoryEventPtr BindMemoryInfo::GetVulkanMemoryEvent() { auto event = make_unique(); // event->source will be set by the object that this bind memory info is // attached to, which can be a buffer or an image. - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_BIND; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_BIND; event->timestamp = timestamp_; event->has_device_memory = true; event->device_memory = device_memory_handle_; @@ -369,10 +365,10 @@ CreateBufferInfo::CreateBufferInfo(VkBufferCreateInfo const* create_buffer_info, VulkanMemoryEventPtr CreateBufferInfo::GetVulkanMemoryEvent() { auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_BUFFER; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_CREATE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_BUFFER; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_CREATE; event->timestamp = timestamp; event->has_device = true; event->device = (uint64_t)(device); @@ -416,8 +412,8 @@ VulkanMemoryEventContainerPtr Buffer::GetVulkanMemoryEvents() { if (is_bound) { auto bind_event = bind_buffer_info->GetVulkanMemoryEvent(); - bind_event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_BUFFER; + bind_event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_BUFFER; bind_event->has_memory_size = true; bind_event->memory_size = memory_size; bind_event->has_object_handle = true; @@ -452,10 +448,10 @@ CreateImageInfo::CreateImageInfo(VkImageCreateInfo const* create_image_info, VulkanMemoryEventPtr CreateImageInfo::GetVulkanMemoryEvent() { auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_IMAGE; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_CREATE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_IMAGE; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_CREATE; event->timestamp = timestamp; event->has_device = true; event->device = (uint64_t)(device); @@ -520,8 +516,8 @@ VulkanMemoryEventContainerPtr Image::GetVulkanMemoryEvents() { if (is_bound) { auto bind_event = bind_image_info->GetVulkanMemoryEvent(); - bind_event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_IMAGE; + bind_event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_IMAGE; bind_event->has_memory_size = true; bind_event->memory_size = memory_size; events->push_back(std::move(bind_event)); @@ -542,10 +538,10 @@ DeviceMemory::DeviceMemory(VkDeviceMemory memory_, VulkanMemoryEventPtr DeviceMemory::GetVulkanMemoryEvent() { auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DEVICE_MEMORY; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_CREATE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DEVICE_MEMORY; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_CREATE; event->timestamp = timestamp; event->has_object_handle = true; event->object_handle = unique_handle; @@ -892,10 +888,10 @@ void PhysicalDevice::DestroyImage(VkImage vk_image) { VulkanMemoryEventPtr PhysicalDevice::GetVulkanMemoryEvent(VkDevice device) { auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DEVICE; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_ANNOTATIONS; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DEVICE; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_ANNOTATIONS; event->timestamp = timestamp; event->has_object_handle = true; event->object_handle = (uint64_t)(device); @@ -1019,10 +1015,10 @@ VulkanMemoryEventContainerSetPtr Device::GetVulkanMemoryEvents() { auto events = make_unique(); // Add an event for the device itself auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DEVICE; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_CREATE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DEVICE; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_CREATE; event->timestamp = timestamp; event->has_object_handle = true; event->object_handle = (uint64_t)(device); @@ -1070,10 +1066,10 @@ VulkanMemoryEventContainerSetPtr Device::GetVulkanMemoryEvents() { VulkanMemoryEventPtr HostAllocation::GetVulkanMemoryEvent() { auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DRIVER; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_CREATE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DRIVER; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_CREATE; event->timestamp = timestamp; event->has_memory_address = true; event->memory_address = ptr; @@ -1308,10 +1304,10 @@ void MemoryTracker::EmitCreateDeviceEvent(VkPhysicalDevice physical_device, make_unique(device, physical_devices[physical_device]); } auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DEVICE; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_CREATE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DEVICE; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_CREATE; event->timestamp = perfetto::base::GetBootTimeNs().count(); event->has_object_handle = true; event->object_handle = (uint64_t)(device); @@ -1321,10 +1317,10 @@ void MemoryTracker::EmitCreateDeviceEvent(VkPhysicalDevice physical_device, void MemoryTracker::EmitDestoryDeviceEvent(VkDevice device) { EmitAllStoredEventsIfNecessary(); auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DEVICE; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_DESTROY; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DEVICE; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_DESTROY; event->timestamp = perfetto::base::GetBootTimeNs().count(); event->has_object_handle = true; event->object_handle = (uint64_t)(device); @@ -1358,10 +1354,10 @@ void MemoryTracker::EmitFreeMemoryEvent(VkDevice device, VkDeviceMemory device_memory) { EmitAllStoredEventsIfNecessary(); auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DEVICE_MEMORY; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_DESTROY; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DEVICE_MEMORY; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_DESTROY; event->timestamp = perfetto::base::GetBootTimeNs().count(); event->has_device = true; event->device = (uint64_t)(device); @@ -1405,8 +1401,8 @@ void MemoryTracker::EmitBindBufferEvent(VkDevice device, VkBuffer buffer, device_memory, global_unique_handles[(uint64_t)(device_memory)], offset, memory_type); auto event = bindinfo.GetVulkanMemoryEvent(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_BUFFER; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_BUFFER; event->has_device = true; event->device = (uint64_t)(device); event->has_heap = true; @@ -1429,10 +1425,10 @@ void MemoryTracker::EmitBindBufferEvent(VkDevice device, VkBuffer buffer, void MemoryTracker::EmitDestroyBufferEvent(VkDevice device, VkBuffer buffer) { EmitAllStoredEventsIfNecessary(); auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_BUFFER; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_DESTROY; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_BUFFER; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_DESTROY; event->timestamp = perfetto::base::GetBootTimeNs().count(); event->has_device = true; event->device = (uint64_t)(device); @@ -1476,8 +1472,8 @@ void MemoryTracker::EmitBindImageEvent(VkDevice device, VkImage image, device_memory, global_unique_handles[(uint64_t)(device_memory)], offset, memory_type); auto event = bindinfo.GetVulkanMemoryEvent(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_IMAGE; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_IMAGE; event->has_device = true; event->device = (uint64_t)(device); event->has_heap = true; @@ -1500,10 +1496,10 @@ void MemoryTracker::EmitBindImageEvent(VkDevice device, VkImage image, void MemoryTracker::EmitDestroyImageEvent(VkDevice device, VkImage image) { EmitAllStoredEventsIfNecessary(); auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_IMAGE; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_DESTROY; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_IMAGE; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_DESTROY; event->timestamp = perfetto::base::GetBootTimeNs().count(); event->has_device = true; event->device = (uint64_t)(device); @@ -1540,10 +1536,10 @@ void MemoryTracker::EmitHostMemoryReallocationEvent( void MemoryTracker::EmitHostMemoryFreeEvent(uintptr_t ptr) { EmitAllStoredEventsIfNecessary(); auto event = make_unique(); - event->source = perfetto::protos::pbzero::VulkanMemoryEvent_Source:: - VulkanMemoryEvent_Source_SOURCE_DRIVER; - event->operation = perfetto::protos::pbzero::VulkanMemoryEvent_Operation:: - VulkanMemoryEvent_Operation_OP_DESTROY; + event->source = + perfetto::protos::pbzero::VulkanMemoryEvent_Source::SOURCE_DRIVER; + event->operation = + perfetto::protos::pbzero::VulkanMemoryEvent_Operation::OP_DESTROY; event->timestamp = perfetto::base::GetBootTimeNs().count(); event->has_memory_address = true; event->memory_address = ptr; diff --git a/core/vulkan/vk_virtual_swapchain/cc/BUILD.bazel b/core/vulkan/vk_virtual_swapchain/cc/BUILD.bazel index 940b56480b..fc31b6c98f 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/BUILD.bazel +++ b/core/vulkan/vk_virtual_swapchain/cc/BUILD.bazel @@ -23,9 +23,9 @@ cc_library( copts = cc_copts() + select({ "//tools/build:linux": [ "-DVK_USE_PLATFORM_XCB_KHR", - "-DVK_USE_PLATFORM_GGP", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], @@ -36,16 +36,17 @@ cc_library( linkopts = select({ "//tools/build:linux": ["-lpthread"], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-lpthread"], # Android "//conditions:default": [ "-ldl", "-lm", + "-llog", ], }), visibility = ["//visibility:public"], deps = [ - "//core/vulkan/cc/include/ggp_c:vulkan_ggp_dummy", "//core/vulkan/tools", "@vulkan-headers//:vulkan", ], @@ -61,6 +62,7 @@ cc_library( "-DVK_USE_PLATFORM_XCB_KHR", ], "//tools/build:darwin": [], + "//tools/build:darwin_arm64": [], "//tools/build:windows": ["-DVK_USE_PLATFORM_WIN32_KHR"], # Android "//conditions:default": ["-DVK_USE_PLATFORM_ANDROID_KHR"], diff --git a/core/vulkan/vk_virtual_swapchain/cc/layer.cpp b/core/vulkan/vk_virtual_swapchain/cc/layer.cpp index 7bec1b3448..e2fb6906df 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/layer.cpp +++ b/core/vulkan/vk_virtual_swapchain/cc/layer.cpp @@ -14,23 +14,76 @@ * limitations under the License. */ -#include +#include "layer.h" + #include -#include +#include #include #include #include #include #include + #include "swapchain.h" +#if defined(__ANDROID__) +#include +#include +#else +#include +#endif + #define LAYER_NAME "VirtualSwapchain" #define LAYER_NAME_FUNCTION(fn) VirtualSwapchain##fn namespace swapchain { +namespace { + +#if defined(__ANDROID__) +bool GetAndroidProperty(const char* const property, std::string* value) { + char buff[PROP_VALUE_MAX]; + if (__system_property_get(property, buff) <= 0) { + return false; + } + *value = buff; + return true; +} +#endif + +} // namespace + +#if defined(__ANDROID__) +void write_warning(const char* message) { + __android_log_print(ANDROID_LOG_WARN, "VirtualSwapchainLayer", "%s", message); +} +#else +void write_warning(const char* message) { + std::cerr << "VirtualSwapchainLayer: " << message << std::endl; +} +#endif + +void write_warning(const std::string& message) { + write_warning(message.c_str()); +} + +bool GetParameter(const char* const env_var_name, + const char* const android_prop_name, + std::string* param_value) { +#if defined(__ANDROID__) + return GetAndroidProperty(android_prop_name, param_value); +#else + const char* const env_var_value = std::getenv(env_var_name); + if (!env_var_value) { + return false; + } + *param_value = env_var_value; + return true; +#endif +} + Context& GetGlobalContext() { // We rely on C++11 static initialization rules here. // kContext will get allocated on first use, and freed in the @@ -138,9 +191,6 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateInstance( #ifdef VK_USE_PLATFORM_ANDROID_KHR GET_PROC(vkCreateAndroidSurfaceKHR); #endif -#ifdef VK_USE_PLATFORM_GGP - GET_PROC(vkCreateStreamDescriptorSurfaceGGP); -#endif #ifdef VK_USE_PLATFORM_XCB_KHR GET_PROC(vkCreateXcbSurfaceKHR); #endif @@ -466,7 +516,9 @@ vkGetInstanceProcAddr(VkInstance instance, const char* funcName) { INTERCEPT(vkGetPhysicalDeviceSurfaceSupportKHR); INTERCEPT(vkGetPhysicalDeviceSurfaceFormatsKHR); + INTERCEPT(vkGetPhysicalDeviceSurfaceFormats2KHR); INTERCEPT(vkGetPhysicalDeviceSurfaceCapabilitiesKHR); + INTERCEPT(vkGetPhysicalDeviceSurfaceCapabilities2KHR); INTERCEPT(vkGetPhysicalDeviceSurfacePresentModesKHR); // From here down it is just functions that have to be overriden for swapchain @@ -499,7 +551,6 @@ vkGetInstanceProcAddr(VkInstance instance, const char* funcName) { INTERCEPT_SURFACE(vkCreateWin32SurfaceKHR); INTERCEPT_SURFACE(vkCreateXcbSurfaceKHR); INTERCEPT_SURFACE(vkCreateXlibSurfaceKHR); - INTERCEPT_SURFACE(vkCreateStreamDescriptorSurfaceGGP); INTERCEPT_SURFACE(vkCreateMacOSSurfaceMVK); #undef INTERCEPT_SURFACE diff --git a/core/vulkan/vk_virtual_swapchain/cc/layer.h b/core/vulkan/vk_virtual_swapchain/cc/layer.h index 71a1f48150..74d90f0902 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/layer.h +++ b/core/vulkan/vk_virtual_swapchain/cc/layer.h @@ -19,12 +19,27 @@ #include #include + #include "vulkan/vulkan.h" #include "threading.h" +#define EXPECT_SUCCESS(fn) \ + [&]() { \ + auto r = fn; \ + if (VK_SUCCESS != r) { \ + swapchain::write_warning(__FILE__ ":" + std::to_string(__LINE__) + \ + ": " #fn " RETURNED: " + std::to_string(r)); \ + } \ + return r; \ + }() + namespace swapchain { +void write_warning(const char* message); + +void write_warning(const std::string& message); + // Sets the key of the dispatch tables used in lower layers of the parent // dispatchable handle to the new child dispatchable handle. This is necessary // as lower layers may use that key to find the dispatch table, and a child @@ -52,9 +67,6 @@ struct InstanceData { #ifdef VK_USE_PLATFORM_ANDROID_KHR PFN_vkCreateAndroidSurfaceKHR vkCreateAndroidSurfaceKHR; #endif -#ifdef VK_USE_PLATFORM_GGP - PFN_vkCreateStreamDescriptorSurfaceGGP vkCreateStreamDescriptorSurfaceGGP; -#endif #ifdef VK_USE_PLATFORM_XCB_KHR PFN_vkCreateXcbSurfaceKHR vkCreateXcbSurfaceKHR; #endif @@ -278,6 +290,9 @@ struct Context { Context& GetGlobalContext(); +bool GetParameter(const char* env_var_name, const char* android_prop_name, + std::string* param_value); + } // namespace swapchain #endif // VK_VIRTUAL_SWAPCHAIN_LAYER_H diff --git a/core/vulkan/vk_virtual_swapchain/cc/platform.cpp b/core/vulkan/vk_virtual_swapchain/cc/platform.cpp index 48f9105a9a..cf00c08c34 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/platform.cpp +++ b/core/vulkan/vk_virtual_swapchain/cc/platform.cpp @@ -59,21 +59,6 @@ void CreateSurface(const InstanceData* functions, VkInstance instance, } } #endif -#ifdef VK_USE_PLATFORM_GGP - { - auto pCreateInfo = - static_cast(data); - if (pCreateInfo->sType == - VK_STRUCTURE_TYPE_STREAM_DESCRIPTOR_SURFACE_CREATE_INFO_GGP) { - // Attempt to create ggp surface - if (functions->vkCreateStreamDescriptorSurfaceGGP( - instance, pCreateInfo, pAllocator, pSurface) != VK_SUCCESS) { - *pSurface = 0; - } - return; - } - } -#endif } } // namespace swapchain diff --git a/core/vulkan/vk_virtual_swapchain/cc/swapchain.cpp b/core/vulkan/vk_virtual_swapchain/cc/swapchain.cpp index 036a42ff5f..33b5613a10 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/swapchain.cpp +++ b/core/vulkan/vk_virtual_swapchain/cc/swapchain.cpp @@ -15,12 +15,52 @@ */ #include "swapchain.h" + #include +#include #include + #include "virtual_swapchain.h" namespace swapchain { +// Used to set the value of VkSurfaceCapabilitiesKHR->currentExtent +// returned from vkGetPhysicalDeviceSurfaceCapabilitiesKHR. +// E.g. VIRTUAL_SWAPCHAIN_SURFACE_EXTENT="1960 1080" +// If unset then the current extent will be the "special value" +// {0xFFFFFFFF, 0xFFFFFFFF}, which some apps don't handle well. +// I.e. they will try to create a swapchain with this maximum extent size and we +// will then fail to create a buffer of this size. +const char* kOverrideSurfaceExtentEnv = "VIRTUAL_SWAPCHAIN_SURFACE_EXTENT"; +// Android property names must be under 32 characters in Android N and below. +const char* kOverrideSurfaceExtentAndroidProp = "debug.vsc.surface_extent"; + +namespace { + +void OverrideCurrentExtentIfNecessary(VkExtent2D* current_extent) { + std::string overridden_extent; + if (GetParameter(kOverrideSurfaceExtentEnv, kOverrideSurfaceExtentAndroidProp, + &overridden_extent)) { + std::istringstream ss(overridden_extent); + VkExtent2D extent; + ss >> extent.width; + if (ss.fail()) { + write_warning("Failed to parse surface extent parameter: " + + overridden_extent); + return; + } + ss >> extent.height; + if (ss.fail()) { + write_warning("Failed to parse surface extent parameter: " + + overridden_extent); + return; + } + *current_extent = extent; + } +} + +} // namespace + void RegisterInstance(VkInstance instance, const InstanceData& data) { uint32_t num_devices = 0; data.vkEnumeratePhysicalDevices(instance, &num_devices, nullptr); @@ -45,6 +85,7 @@ void RegisterInstance(VkInstance instance, const InstanceData& data) { // vkCreateXXXSurface calls. struct VirtualSurface { bool always_return_given_surface_formats_and_present_modes; + VkExtent2D current_extent; }; VKAPI_ATTR VkResult VKAPI_CALL vkCreateVirtualSurface( @@ -52,6 +93,8 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateVirtualSurface( const VkAllocationCallbacks* pAllocator, VkSurfaceKHR* pSurface) { auto* surf = new VirtualSurface(); surf->always_return_given_surface_formats_and_present_modes = false; + surf->current_extent = {0xFFFFFFFF, 0xFFFFFFFF}; + if (pCreateInfo != nullptr) { for (const CreateNext* pNext = static_cast(pCreateInfo->pNext); @@ -62,6 +105,9 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateVirtualSurface( } } } + + OverrideCurrentExtentIfNecessary(&surf->current_extent); + *pSurface = reinterpret_cast(surf); return VK_SUCCESS; } @@ -108,9 +154,11 @@ VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR( .GetPhysicalDeviceData(physicalDevice) ->physical_device_properties_; + VirtualSurface* suf = reinterpret_cast(surface); + pSurfaceCapabilities->minImageCount = 1; pSurfaceCapabilities->maxImageCount = 0; - pSurfaceCapabilities->currentExtent = {0xFFFFFFFF, 0xFFFFFFFF}; + pSurfaceCapabilities->currentExtent = suf->current_extent; pSurfaceCapabilities->minImageExtent = {1, 1}; pSurfaceCapabilities->maxImageExtent = { properties.limits.maxImageDimension2D, @@ -134,6 +182,15 @@ VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR( return VK_SUCCESS; } +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilities2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + VkSurfaceCapabilities2KHR* pSurfaceCapabilities) { + return swapchain::vkGetPhysicalDeviceSurfaceCapabilitiesKHR( + physicalDevice, pSurfaceInfo->surface, + &pSurfaceCapabilities->surfaceCapabilities); +} + VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats) { @@ -156,6 +213,15 @@ VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR( return VK_SUCCESS; } +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormats2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats) { + return swapchain::vkGetPhysicalDeviceSurfaceFormatsKHR( + physicalDevice, pSurfaceInfo->surface, pSurfaceFormatCount, + pSurfaceFormats); +} + VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes) { @@ -335,11 +401,16 @@ vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo) { // We submit to the queue the commands set up by the virtual swapchain. // This will start a copy operation from the image to the swapchain // buffers. - uint32_t res = VK_SUCCESS; + + VkResult res = VK_SUCCESS; + std::vector pipeline_stages( pPresentInfo->waitSemaphoreCount, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - for (size_t i = 0; i < pPresentInfo->swapchainCount; ++i) { + + size_t i = 0; + for (; i < pPresentInfo->swapchainCount; ++i) { uint32_t image_index = pPresentInfo->pImageIndices[i]; + VirtualSwapchain* swp = reinterpret_cast(pPresentInfo->pSwapchains[i]); @@ -355,13 +426,33 @@ vkQueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* pPresentInfo) { nullptr // pSemaphores }; - res |= GetGlobalContext().GetQueueData(queue)->vkQueueSubmit( - queue, 1, &submitInfo, swp->GetFence(image_index)); - res |= swp->PresentToSurface(queue, image_index); + res = EXPECT_SUCCESS(GetGlobalContext().GetQueueData(queue)->vkQueueSubmit( + queue, 1, &submitInfo, swp->GetFence(image_index))); + + if (res != VK_SUCCESS) { + break; + } + + res = swp->PresentToSurface(queue, image_index); + if (res != VK_SUCCESS) { + break; + } + swp->NotifySubmitted(image_index); + + if (pPresentInfo->pResults) { + pPresentInfo->pResults[i] = VK_SUCCESS; + } } - return VkResult(res); + // If we left the above loop early, then set the remaining results as errors. + if (pPresentInfo->pResults) { + for (; i < pPresentInfo->swapchainCount; ++i) { + pPresentInfo->pResults[i] = res; + } + } + + return res; } VKAPI_ATTR VkResult VKAPI_CALL vkQueueSubmit(VkQueue queue, @@ -395,10 +486,14 @@ VKAPI_ATTR void VKAPI_CALL vkCmdPipelineBarrier( if (imageBarriers[i].oldLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { imageBarriers[i].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; imageBarriers[i].srcAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + // Ensure the stage mask supports the transfer read access. + srcStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; } if (imageBarriers[i].newLayout == VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) { imageBarriers[i].newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; imageBarriers[i].dstAccessMask |= VK_ACCESS_TRANSFER_READ_BIT; + // Ensure the stage mask supports the transfer read access. + dstStageMask |= VK_PIPELINE_STAGE_TRANSFER_BIT; } } PFN_vkCmdPipelineBarrier func = GetGlobalContext() diff --git a/core/vulkan/vk_virtual_swapchain/cc/swapchain.h b/core/vulkan/vk_virtual_swapchain/cc/swapchain.h index 8c3fe4210f..d51bf16082 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/swapchain.h +++ b/core/vulkan/vk_virtual_swapchain/cc/swapchain.h @@ -46,10 +46,20 @@ VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilitiesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR* pSurfaceCapabilities); +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceCapabilities2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + VkSurfaceCapabilities2KHR* pSurfaceCapabilities); + VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats); +VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormats2KHR( + VkPhysicalDevice physicalDevice, + const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, + uint32_t* pSurfaceFormatCount, VkSurfaceFormatKHR* pSurfaceFormats); + VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfacePresentModesKHR( VkPhysicalDevice physicalDevice, VkSurfaceKHR surface, uint32_t* pPresentModeCount, VkPresentModeKHR* pPresentModes); @@ -117,4 +127,4 @@ VKAPI_ATTR VkResult VKAPI_CALL vkCreateRenderPass( VkDevice device, const VkRenderPassCreateInfo* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass); } // namespace swapchain -#endif // VK_VIRTUAL_SWAPCHAIN_SWAPCHAIN_H_ \ No newline at end of file +#endif // VK_VIRTUAL_SWAPCHAIN_SWAPCHAIN_H_ diff --git a/core/vulkan/vk_virtual_swapchain/cc/virtual_swapchain.cpp b/core/vulkan/vk_virtual_swapchain/cc/virtual_swapchain.cpp index 500d32bb50..571ddeeea6 100644 --- a/core/vulkan/vk_virtual_swapchain/cc/virtual_swapchain.cpp +++ b/core/vulkan/vk_virtual_swapchain/cc/virtual_swapchain.cpp @@ -15,14 +15,13 @@ */ #include "virtual_swapchain.h" + #include #include #include #include #include #include -#include -#include #include #include @@ -41,12 +40,15 @@ int32_t FindMemoryType( properties)) return i; } + swapchain::write_warning("FindMemoryType returned -1"); return -1; } void null_callback(void*, uint8_t*, size_t) {} +// Android property names must be under 32 characters in Android N and below. const char* kImageDumpPathEnv = "IMAGE_DUMP_PATH"; +const char* kImageDumpPathAndroidProp = "debug.vsc.image_dump_path"; void WritePngFile(std::unique_ptr image_data, size_t size, std::string file_name, uint32_t width, uint32_t height, @@ -88,6 +90,15 @@ VirtualSwapchain::VirtualSwapchain( always_get_acquired_image_(always_get_acquired_image), base_swapchain_(nullptr) { VkPhysicalDeviceMemoryProperties properties = *memory_properties; + VkCommandPoolCreateInfo command_pool_info{ + VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, // sType + nullptr, // pNext + VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // flags + queue_ // queueFamilyIndex + }; + EXPECT_SUCCESS(functions_->vkCreateCommandPool(device_, &command_pool_info, + pAllocator, &command_pool_)); + build_swapchain_image_data_ = [this, properties, pAllocator]() { SwapchainImageData image_data; @@ -137,16 +148,6 @@ VirtualSwapchain::VirtualSwapchain( nullptr // pQueueFamilyIndices }; - VkCommandPoolCreateInfo command_pool_info{ - VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, // sType - nullptr, // pNext - VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, // flags - queue_ // queueFamilyIndex - }; - - functions_->vkCreateCommandPool(device_, &command_pool_info, pAllocator, - &command_pool_); - VkCommandBufferAllocateInfo command_buffer_info{ VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, // sType nullptr, // pNext @@ -156,21 +157,24 @@ VirtualSwapchain::VirtualSwapchain( }; // Create the command buffer - functions_->vkAllocateCommandBuffers(device_, &command_buffer_info, - &image_data.command_buffer_); + EXPECT_SUCCESS(functions_->vkAllocateCommandBuffers( + device_, &command_buffer_info, &image_data.command_buffer_)); + set_dispatch_from_parent(image_data.command_buffer_, device_); // Create the fence { - functions_->vkCreateFence(device_, &fence_info, pAllocator, - &image_data.fence_); - functions_->vkResetFences(device_, 1, &image_data.fence_); + EXPECT_SUCCESS(functions_->vkCreateFence(device_, &fence_info, pAllocator, + &image_data.fence_)); + + EXPECT_SUCCESS(functions_->vkResetFences(device_, 1, &image_data.fence_)); } // Create the buffer { - functions_->vkCreateBuffer(device_, &buffer_create_info, pAllocator, - &image_data.buffer_); + EXPECT_SUCCESS(functions_->vkCreateBuffer( + device_, &buffer_create_info, pAllocator, &image_data.buffer_)); + // Create device-memory for the buffer { VkMemoryRequirements reqs; @@ -180,6 +184,7 @@ VirtualSwapchain::VirtualSwapchain( uint32_t memory_type = FindMemoryType(&properties, reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + VkMemoryAllocateInfo buffer_memory_info{ VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, // sType nullptr, // pNext @@ -187,17 +192,20 @@ VirtualSwapchain::VirtualSwapchain( memory_type // memoryTypeIndex }; - functions_->vkAllocateMemory(device_, &buffer_memory_info, pAllocator, - &image_data.buffer_memory_); - functions_->vkBindBufferMemory(device_, image_data.buffer_, - image_data.buffer_memory_, 0); + EXPECT_SUCCESS(functions_->vkAllocateMemory( + device_, &buffer_memory_info, pAllocator, + &image_data.buffer_memory_)); + + EXPECT_SUCCESS(functions_->vkBindBufferMemory( + device_, image_data.buffer_, image_data.buffer_memory_, 0)); } } // Create the image { - functions_->vkCreateImage(device_, &image_create_info, pAllocator, - &image_data.image_); + EXPECT_SUCCESS(functions_->vkCreateImage(device_, &image_create_info, + pAllocator, &image_data.image_)); + // Create device-memory for the image { VkMemoryRequirements reqs; @@ -213,10 +221,17 @@ VirtualSwapchain::VirtualSwapchain( memory_type // memoryTypeIndex }; - functions_->vkAllocateMemory(device_, &image_memory_info, pAllocator, - &image_data.image_memory_); - functions_->vkBindImageMemory(device_, image_data.image_, - image_data.image_memory_, 0); + VkResult res = EXPECT_SUCCESS(functions_->vkAllocateMemory( + device_, &image_memory_info, pAllocator, + &image_data.image_memory_)); + if (res != VK_SUCCESS) { + swapchain::write_warning( + "HINT: try setting the default surface extent parameter of the " + "Virtual Swapchain layer."); + } + + EXPECT_SUCCESS(functions_->vkBindImageMemory( + device_, image_data.image_, image_data.image_memory_, 0)); } } @@ -252,14 +267,18 @@ VirtualSwapchain::VirtualSwapchain( nullptr // pInheritanceInfo }; - functions_->vkBeginCommandBuffer(image_data.command_buffer_, &cbegin); + EXPECT_SUCCESS( + functions_->vkBeginCommandBuffer(image_data.command_buffer_, &cbegin)); + functions_->vkCmdCopyImageToBuffer( image_data.command_buffer_, image_data.image_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image_data.buffer_, 1, ®ion); + functions_->vkCmdPipelineBarrier( image_data.command_buffer_, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 0, nullptr, 1, &dest_barrier, 0, 0); - functions_->vkEndCommandBuffer(image_data.command_buffer_); + + EXPECT_SUCCESS(functions_->vkEndCommandBuffer(image_data.command_buffer_)); return image_data; }; @@ -285,10 +304,7 @@ VirtualSwapchain::VirtualSwapchain( }, this); #endif - const char* const image_dump_dir = std::getenv(kImageDumpPathEnv); - if (image_dump_dir != nullptr) { - image_dump_dir_ = std::string(image_dump_dir); - } + GetParameter(kImageDumpPathEnv, kImageDumpPathAndroidProp, &image_dump_dir_); } void VirtualSwapchain::Destroy(const VkAllocationCallbacks* pAllocator) { @@ -353,14 +369,17 @@ void VirtualSwapchain::CopyThreadFunc() { } } - VkResult ret = functions_->vkWaitForFences( - device_, 1, &image_data_[pending_image].fence_, false, UINT64_MAX); - (void)ret; // TODO: Check this? - functions_->vkResetFences(device_, 1, &image_data_[pending_image].fence_); + EXPECT_SUCCESS(functions_->vkWaitForFences( + device_, 1, &image_data_[pending_image].fence_, false, UINT64_MAX)); + + EXPECT_SUCCESS(functions_->vkResetFences( + device_, 1, &image_data_[pending_image].fence_)); void* mapped_value; - functions_->vkMapMemory(device_, image_data_[pending_image].buffer_memory_, - 0, VK_WHOLE_SIZE, 0, &mapped_value); + EXPECT_SUCCESS(functions_->vkMapMemory( + device_, image_data_[pending_image].buffer_memory_, 0, VK_WHOLE_SIZE, 0, + &mapped_value)); + VkMappedMemoryRange range{ VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, // sType nullptr, // pNext @@ -368,12 +387,14 @@ void VirtualSwapchain::CopyThreadFunc() { 0, // offset VK_WHOLE_SIZE, // size }; - functions_->vkInvalidateMappedMemoryRanges(device_, 1, &range); + + EXPECT_SUCCESS( + functions_->vkInvalidateMappedMemoryRanges(device_, 1, &range)); uint32_t length = ImageByteSize(); { callback_(callback_user_data_, (uint8_t*)mapped_value, length); - if (image_dump_dir_ != "") { + if (!image_dump_dir_.empty()) { DumpImageToFile((uint8_t*)mapped_value, length); } } diff --git a/gapic/BUILD.bazel b/gapic/BUILD.bazel index 853c52ab42..e571360e57 100644 --- a/gapic/BUILD.bazel +++ b/gapic/BUILD.bazel @@ -16,6 +16,7 @@ java_binary( name = "gapic", jvm_flags = select({ "//tools/build:darwin": ["-XstartOnFirstThread"], + "//tools/build:darwin_arm64": ["-XstartOnFirstThread"], "//tools/build:linux": [ "-DSWT_GTK3=0", "-DLIBOVERLAY_SCROLLBAR=0", diff --git a/gapic/README.md b/gapic/README.md new file mode 100644 index 0000000000..ceb1c56e23 --- /dev/null +++ b/gapic/README.md @@ -0,0 +1,35 @@ +## Setting up Eclipse to build the AGI UI + +1. Create the `bazel-external` link in the AGI checkout directory: + - Linux: + `ln -s $(bazel info output_base)/external bazel-external` + - Windows Command Prompt: + ``for /f usebackq %F in (`bazel info output_base`) do mklink /J bazel-external "%F/external"`` +2. Create and open a new Eclipse workspace. +3. Open the Eclipse Preferences and navigate to **General** -> **Workspace** -> **Linked Resources**. +4. Click **New...** to define a new path variable: + 1. Set the name to `GAPIC_PLATFORM_SRC`. + 2. Set the value to the your platform's folder inside the gapic/src/platform folder of the AGI checkout, e.g. `/gapic/src/platform/` + 3. Click **OK** to create the variable. +5. Click **Apply and Close** to dismiss the preferences dialog. +6. Run the bazel build to build all the generated code. +7. Select **File** -> **Import** and then **General** -> **Existing Projects into Workspace** and click **Next**. + 1. Enter the location of your AGI checkout into the root directory box. + 2. Click **Select All**. You should see a project named gapic. + 3. **IMPORTANT**: Uncheck **Copy projects into workspace** + 4. Click **Finish**. +8. Set up **Run Configurations**. + 1. Select **Java Application**. + 2. Press the button for a new configuration. + 3. Choose a name, e.g. "Default". + 4. In **Main** tab: + - In **Project** click **Browse**. **gapic** should be selected. Click **Ok**. + - In **Main class** click **Search**, select **Main - com.google.gapid** and click **Ok**. + 5. In **Arguments** tab: + - In **Program arguments** give the `--gapid` flag. E.g.: `--gapid /bazel-bin/pkg`, replace `` with your AGI checkout path. Optionally add more arguments. + 6. In **JRE** tab: + - If AGI should run with a different JRE than Eclipse, choose it in **Alternate JRE**. It might have to be added as **Installed JREs** first. + 7. In **Classpath** tab: + - Select **User Entries** and click **Add JARs**. Select `/gapic/bazel-external/org_lwjgl_core/lwjgl-natives-.jar` for your OS and click **Ok**. Folder name and jar name may vary based on your operating system. + - Repeat for `/gapic/bazel-external/org_lwjgl_opengl/lwjgl-opengl-natives-.jar`. + 8. Click **Apply** and **Run** to test the configuration. diff --git a/gapic/docs/shaders.md b/gapic/docs/shaders.md index b0d020ad73..c7afb19d1b 100644 --- a/gapic/docs/shaders.md +++ b/gapic/docs/shaders.md @@ -6,7 +6,7 @@ the same file. Special "comment markers" are used to indicate which shader the following lines of code belong to. These program files are stored in the -[shader resource](https://github.com/google/gapid/tree/master/gapic/res/shaders) +[shader resource](https://github.com/google/agi/tree/master/gapic/res/shaders) directory in files ending with the ```.glsl``` extension. The name of the file is used as the identifier of the shader program. diff --git a/gapic/readme.md b/gapic/readme.md deleted file mode 100644 index cabf4a7bcd..0000000000 --- a/gapic/readme.md +++ /dev/null @@ -1,21 +0,0 @@ -## Setting up Eclipse to build GAPIC - -1. Create the `bazel-external` link: - 1. `ln -s $(bazel info output_base)/external bazel-external` -2. Create and open a new Eclipse workspace. -3. Open the Eclipse Preferences and navigate to **General** -> **Workspace** -> **Linked Resources**. -4. Click **New...** to define a new path variable: - 1. Set the name to `GAPIC_PLATFORM_SRC`. - 2. Set the value to the your platform's folder inside the gapic/src/platform folder of the gapid checkout. - e.g. `/gapic/src/platform/linux` - 3. Click **OK** to create the variable. -5. Click **OK** to dismiss the preferences dialog. -6. Run the bazel build to build all the generated code. -7. Select **File** -> **Import** and then **General** -> **Existing Project into Workspace** and click **Next**. - 1. Enter the location of your gapid checkout into the root directory box. - 2. Click **Select All**. You should see a project named gapic. - 3. **IMPORTANT**: Uncheck **Copy projects into workspace** - 4. Click "Finish". -8. Set up Run Configurations. - 1. In Arguments tab: give the --gapid flag. E.g.: `--gapid /bazel-bin/pkg`, replace `` with your local project path. - 2. In Dependencies tab: add the lwjgl native jars. Find and add `/gapic/bazel-external/org_lwjgl_core_natives_linux/jar/lwjgl-3.2.0.jar`, `/gapic/bazel-external/org_lwjgl_opengl_natives_linux/jar/lwjgl-opengl-3.2.0.jar`. Folder name and jar name may vary based on your operating system. diff --git a/gapic/res/BUILD.bazel b/gapic/res/BUILD.bazel index 7277f65f17..aaa2c170c1 100644 --- a/gapic/res/BUILD.bazel +++ b/gapic/res/BUILD.bazel @@ -12,20 +12,43 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("//tools/build:rules.bzl", "copy_tree") +load("//tools/build:rules.bzl", "copy_to", "copy_tree") # This is an ugly ugly hack # The bazel resource rules expect a specific directory structure, so we copy_resources # into that struture first copy_tree( name = "copy_resources", - srcs = glob(["**/*"]), + srcs = glob( + ["**/*"], + exclude = ["BUILD.bazel"], + ), strip = package_name() + "/", to = "java", ) +copy_to( + name = "copy_logo", + # buildifier: leave-alone + srcs = [ + "//tools/logo:logo_16.png", + "//tools/logo:logo_32.png", + "//tools/logo:logo_48.png", + "//tools/logo:logo_64.png", + "//tools/logo:logo_128.png", + "//tools/logo:logo_256.png", + ], + rename = { + "logo_256.png": "logo_128@2x.png", + }, + to = "java/icons", +) + java_library( name = "res", - resources = [":copy_resources"], + resources = [ + ":copy_logo", + ":copy_resources", + ], visibility = ["//visibility:public"], ) diff --git a/gapic/res/icons/check_circle.png b/gapic/res/icons/check_circle.png new file mode 100644 index 0000000000..6472ef1d81 Binary files /dev/null and b/gapic/res/icons/check_circle.png differ diff --git a/gapic/res/icons/check_circle@2x.png b/gapic/res/icons/check_circle@2x.png new file mode 100644 index 0000000000..a1a0cbc45e Binary files /dev/null and b/gapic/res/icons/check_circle@2x.png differ diff --git a/gapic/res/icons/close.png b/gapic/res/icons/close.png new file mode 100644 index 0000000000..2269480a51 Binary files /dev/null and b/gapic/res/icons/close.png differ diff --git a/gapic/res/icons/close@2x.png b/gapic/res/icons/close@2x.png new file mode 100644 index 0000000000..7d394d8d11 Binary files /dev/null and b/gapic/res/icons/close@2x.png differ diff --git a/gapic/res/icons/expand.png b/gapic/res/icons/expand.png new file mode 100644 index 0000000000..809b535e50 Binary files /dev/null and b/gapic/res/icons/expand.png differ diff --git a/gapic/res/icons/expand@2x.png b/gapic/res/icons/expand@2x.png new file mode 100644 index 0000000000..7a9cf42393 Binary files /dev/null and b/gapic/res/icons/expand@2x.png differ diff --git a/gapic/res/icons/filter.png b/gapic/res/icons/filter.png new file mode 100644 index 0000000000..ac5ee6a62a Binary files /dev/null and b/gapic/res/icons/filter.png differ diff --git a/gapic/res/icons/filter@2x.png b/gapic/res/icons/filter@2x.png new file mode 100644 index 0000000000..7af927b1d3 Binary files /dev/null and b/gapic/res/icons/filter@2x.png differ diff --git a/gapic/res/icons/flag.png b/gapic/res/icons/flag.png new file mode 100644 index 0000000000..b902f6f7ef Binary files /dev/null and b/gapic/res/icons/flag.png differ diff --git a/gapic/res/icons/flag@2x.png b/gapic/res/icons/flag@2x.png new file mode 100644 index 0000000000..61006e1e8e Binary files /dev/null and b/gapic/res/icons/flag@2x.png differ diff --git a/gapic/res/icons/flag_filled.png b/gapic/res/icons/flag_filled.png new file mode 100644 index 0000000000..16372aa2ce Binary files /dev/null and b/gapic/res/icons/flag_filled.png differ diff --git a/gapic/res/icons/flag_filled@2x.png b/gapic/res/icons/flag_filled@2x.png new file mode 100644 index 0000000000..27e31ca2bd Binary files /dev/null and b/gapic/res/icons/flag_filled@2x.png differ diff --git a/gapic/res/icons/flag_greyed.png b/gapic/res/icons/flag_greyed.png new file mode 100644 index 0000000000..a47c0721ce Binary files /dev/null and b/gapic/res/icons/flag_greyed.png differ diff --git a/gapic/res/icons/flag_greyed@2x.png b/gapic/res/icons/flag_greyed@2x.png new file mode 100644 index 0000000000..89a0bc7aca Binary files /dev/null and b/gapic/res/icons/flag_greyed@2x.png differ diff --git a/gapic/res/icons/flag_white.png b/gapic/res/icons/flag_white.png new file mode 100644 index 0000000000..d8a47b617d Binary files /dev/null and b/gapic/res/icons/flag_white.png differ diff --git a/gapic/res/icons/flag_white@2x.png b/gapic/res/icons/flag_white@2x.png new file mode 100644 index 0000000000..40801a862a Binary files /dev/null and b/gapic/res/icons/flag_white@2x.png differ diff --git a/gapic/res/icons/flag_white_filled.png b/gapic/res/icons/flag_white_filled.png new file mode 100644 index 0000000000..15508dc156 Binary files /dev/null and b/gapic/res/icons/flag_white_filled.png differ diff --git a/gapic/res/icons/flag_white_filled@2x.png b/gapic/res/icons/flag_white_filled@2x.png new file mode 100644 index 0000000000..e7b1efe13d Binary files /dev/null and b/gapic/res/icons/flag_white_filled@2x.png differ diff --git a/gapic/res/icons/frame_profiler.png b/gapic/res/icons/frame_profiler.png new file mode 100644 index 0000000000..10490614d1 Binary files /dev/null and b/gapic/res/icons/frame_profiler.png differ diff --git a/gapic/res/icons/frame_profiler@2x.png b/gapic/res/icons/frame_profiler@2x.png new file mode 100644 index 0000000000..b80a664f98 Binary files /dev/null and b/gapic/res/icons/frame_profiler@2x.png differ diff --git a/gapic/res/icons/info.png b/gapic/res/icons/info.png new file mode 100644 index 0000000000..be365a6e92 Binary files /dev/null and b/gapic/res/icons/info.png differ diff --git a/gapic/res/icons/info@2x.png b/gapic/res/icons/info@2x.png new file mode 100644 index 0000000000..a2911fda06 Binary files /dev/null and b/gapic/res/icons/info@2x.png differ diff --git a/gapic/res/icons/logo_1024.png b/gapic/res/icons/logo_1024.png deleted file mode 100644 index 8681ea3eb3..0000000000 Binary files a/gapic/res/icons/logo_1024.png and /dev/null differ diff --git a/gapic/res/icons/logo_128.png b/gapic/res/icons/logo_128.png deleted file mode 100644 index e3f1609214..0000000000 Binary files a/gapic/res/icons/logo_128.png and /dev/null differ diff --git a/gapic/res/icons/logo_128@2x.png b/gapic/res/icons/logo_128@2x.png deleted file mode 100644 index 81dbed512d..0000000000 Binary files a/gapic/res/icons/logo_128@2x.png and /dev/null differ diff --git a/gapic/res/icons/logo_16.png b/gapic/res/icons/logo_16.png deleted file mode 100644 index 8b308b14e8..0000000000 Binary files a/gapic/res/icons/logo_16.png and /dev/null differ diff --git a/gapic/res/icons/logo_256.png b/gapic/res/icons/logo_256.png deleted file mode 100644 index 81dbed512d..0000000000 Binary files a/gapic/res/icons/logo_256.png and /dev/null differ diff --git a/gapic/res/icons/logo_32.png b/gapic/res/icons/logo_32.png deleted file mode 100644 index 1e4245c875..0000000000 Binary files a/gapic/res/icons/logo_32.png and /dev/null differ diff --git a/gapic/res/icons/logo_48.png b/gapic/res/icons/logo_48.png deleted file mode 100644 index 67806a7a6c..0000000000 Binary files a/gapic/res/icons/logo_48.png and /dev/null differ diff --git a/gapic/res/icons/logo_512.png b/gapic/res/icons/logo_512.png deleted file mode 100644 index 4a57584d8d..0000000000 Binary files a/gapic/res/icons/logo_512.png and /dev/null differ diff --git a/gapic/res/icons/logo_64.png b/gapic/res/icons/logo_64.png deleted file mode 100644 index 72bfcd3c39..0000000000 Binary files a/gapic/res/icons/logo_64.png and /dev/null differ diff --git a/gapic/res/icons/science.png b/gapic/res/icons/science.png new file mode 100644 index 0000000000..6159cc59c7 Binary files /dev/null and b/gapic/res/icons/science.png differ diff --git a/gapic/res/icons/science@2x.png b/gapic/res/icons/science@2x.png new file mode 100644 index 0000000000..06957ebe71 Binary files /dev/null and b/gapic/res/icons/science@2x.png differ diff --git a/gapic/res/icons/swap.png b/gapic/res/icons/swap.png new file mode 100644 index 0000000000..c1a5192b0c Binary files /dev/null and b/gapic/res/icons/swap.png differ diff --git a/gapic/res/icons/swap@2x.png b/gapic/res/icons/swap@2x.png new file mode 100644 index 0000000000..94db335d23 Binary files /dev/null and b/gapic/res/icons/swap@2x.png differ diff --git a/gapic/res/icons/system_profiler.png b/gapic/res/icons/system_profiler.png new file mode 100644 index 0000000000..6d58e6c975 Binary files /dev/null and b/gapic/res/icons/system_profiler.png differ diff --git a/gapic/res/icons/system_profiler@2x.png b/gapic/res/icons/system_profiler@2x.png new file mode 100644 index 0000000000..af7eb6a7b3 Binary files /dev/null and b/gapic/res/icons/system_profiler@2x.png differ diff --git a/gapic/res/icons/ydown.png b/gapic/res/icons/ydown.png new file mode 100644 index 0000000000..21736c1481 Binary files /dev/null and b/gapic/res/icons/ydown.png differ diff --git a/gapic/res/icons/zdown.png b/gapic/res/icons/zdown.png new file mode 100644 index 0000000000..1c6cba30ad Binary files /dev/null and b/gapic/res/icons/zdown.png differ diff --git a/gapic/res/text/licenses.html b/gapic/res/text/licenses.html index 2e309ea462..1ddd69b824 100644 --- a/gapic/res/text/licenses.html +++ b/gapic/res/text/licenses.html @@ -1,11 +1,11 @@ - GAPIC Licenses + Android GPU Inspector Licenses -

GAPIC Licenses

+

Android GPU Inspector Licenses

-

GAPID

+

Android GPU Inspector

 Copyright (C) 2017 Google Inc.
 
@@ -768,6 +768,23 @@ 

Eclipse OSGi

property of their respective owners and are hereby recognized.
+

Etc2Comp

+
+Copyright 2015 Etc2Comp 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
+
+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.
+
+

Glslang

 Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
@@ -1059,125 +1076,6 @@ 

IJG.org

CompuServe Incorporated."
-

LLVM

-
-University of Illinois/NCSA
-Open Source License
-
-Copyright (c) 2003-2017 University of Illinois at Urbana-Champaign.
-All rights reserved.
-
-Developed by:
-
-    LLVM Team
-
-    University of Illinois at Urbana-Champaign
-
-    http://llvm.org
-
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal with
-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:
-
-    * Redistributions of source code must retain the above copyright notice,
-      this list of conditions and the following disclaimers.
-
-    * Redistributions in binary form must reproduce the above copyright notice,
-      this list of conditions and the following disclaimers in the
-      documentation and/or other materials provided with the distribution.
-
-    * Neither the names of the LLVM Team, University of Illinois at
-      Urbana-Champaign, nor the names of its contributors may be used to
-      endorse or promote products derived from this Software without specific
-      prior written permission.
-
-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
-CONTRIBUTORS 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 WITH THE
-SOFTWARE.
-
- -

LLVM ARM Contributions

-
-ARM Limited
-
-Software Grant License Agreement ("Agreement")
-
-Except for the license granted herein to you, ARM Limited ("ARM") reserves all
-right, title, and interest in and to the Software (defined below).
-
-Definition
-
-"Software" means the code and documentation as well as any original work of
-authorship, including any modifications or additions to an existing work, that
-is intentionally submitted by ARM to llvm.org (http://llvm.org) ("LLVM") for
-inclusion in, or documentation of, any of the products owned or managed by LLVM
-(the "Work"). For the purposes of this definition, "submitted" means any form of
-electronic, verbal, or written communication sent to LLVM 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, LLVM for the purpose of discussing and improving
-the Work, but excluding communication that is conspicuously marked otherwise.
-
-1. Grant of Copyright License. Subject to the terms and conditions of this
-   Agreement, ARM hereby grants to you and to recipients of the Software
-   distributed by LLVM 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
-   Software and such derivative works.
-
-2. Grant of Patent License. Subject to the terms and conditions of this
-   Agreement, ARM hereby grants you and to recipients of the Software
-   distributed by LLVM 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 ARM that are necessarily infringed by ARM's Software alone or by
-   combination of the Software with the Work to which such Software was
-   submitted. If any entity institutes patent litigation against ARM or any
-   other entity (including a cross-claim or counterclaim in a lawsuit) alleging
-   that ARM's Software, or the Work to which ARM has contributed constitutes
-   direct or contributory patent infringement, then any patent licenses granted
-   to that entity under this Agreement for the Software or Work shall terminate
-   as of the date such litigation is filed.
-
-Unless required by applicable law or agreed to in writing, the software is
-provided 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.
-
- -

LLVM md5 Contribution

-
-This is an OpenSSL-compatible implementation of the RSA Data Security, Inc.
-MD5 Message-Digest Algorithm (RFC 1321).
-
-Homepage:
-http://openwall.info/wiki/people/solar/software/public-domain-source-code/md5
-
-Author:
-Alexander Peslyak, better known as Solar Designer 
-
-This software was written by Alexander Peslyak in 2001.  No copyright is
-claimed, and the software is hereby placed in the public domain.
-In case this attempt to disclaim copyright and place the software in the
-public domain is deemed null and void, then the software is
-Copyright (c) 2001 Alexander Peslyak and it is hereby released to the
-general public under the following terms:
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted.
-
-There's ABSOLUTELY NO WARRANTY, express or implied.
-
-

LWJGL

 Copyright 2012-present Lightweight Java Game Library
@@ -2417,5 +2315,12 @@ 

zlib

the FAQ for more information on the distribution of modified source versions.
+

OpenJDK JRE

+ +

Some versions of Android GPU Inspector contain a bundled version of OpenJDK. +We make the binaries and source-code of the versions we bundle available for +download here: http://jdk-mirror.storage.googleapis.com/index.html +

+ diff --git a/gapic/src/main/BUILD.bazel b/gapic/src/main/BUILD.bazel index fbf3fde0e5..f49a03ff96 100644 --- a/gapic/src/main/BUILD.bazel +++ b/gapic/src/main/BUILD.bazel @@ -12,7 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("//:version.bzl", "gapid_version") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("//:version.bzl", "agi_version") load("//tools/build:rules.bzl", "java_grpc_library") java_library( @@ -33,10 +34,22 @@ java_library( ], ) -gapid_version( +agi_version( name = "version", out = "com/google/gapid/util/GapidVersion.java", - template = "com/google/gapid/util/GapidVersion.java.in", + template = ":version_with_build_year", +) + +genrule( + name = "version_with_build_year", + srcs = ["com/google/gapid/util/GapidVersion.java.in"], + outs = ["com/google/gapid/util/GapidVersion.java.out"], + cmd = ";".join([ + "V=$$(grep BUILD_TIMESTAMP bazel-out/volatile-status.txt | cut -d ' ' -f 2)", + "Y=$$([ $$(uname) = 'Darwin' ] && date -jf %s $$V +%Y || date -d @$$V +%Y)", + "sed -e s/@AGI_BUILD_YEAR@/$$Y/ $< > $@", + ]), + stamp = 1, ) java_grpc_library( @@ -44,6 +57,7 @@ java_grpc_library( srcs = ["//gapis/service:service_proto"], deps = [ ":protos", + "@com_google_protobuf//:protobuf_java", "@gapic_third_party//:grpc", "@gapic_third_party//:guava", ], @@ -77,7 +91,9 @@ proto_library( name = "settings_proto", srcs = ["com/google/gapid/settings.proto"], deps = [ - "@perfetto//:protos_perfetto_config_merged_config_protos", + "//core/os/device:device_proto", + "//gapis/service:service_proto", + "@perfetto//:protos_perfetto_config_protos", ], ) diff --git a/gapic/src/main/com/google/gapid/GraphicsTraceView.java b/gapic/src/main/com/google/gapid/GraphicsTraceView.java index 18bd27db3a..edf714c5e4 100644 --- a/gapic/src/main/com/google/gapid/GraphicsTraceView.java +++ b/gapic/src/main/com/google/gapid/GraphicsTraceView.java @@ -18,31 +18,42 @@ import static java.util.stream.Collectors.toList; import com.google.common.base.Splitter; +import com.google.common.collect.ListMultimap; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.MultimapBuilder; import com.google.common.collect.Sets; +import com.google.gapid.models.Analytics; import com.google.gapid.models.Analytics.View; import com.google.gapid.models.Follower; import com.google.gapid.models.Models; +import com.google.gapid.models.Resources; import com.google.gapid.models.Settings; import com.google.gapid.proto.SettingsProto; -import com.google.gapid.proto.service.Service.ClientAction; +import com.google.gapid.proto.service.Service; +import com.google.gapid.proto.service.Service.Resource; import com.google.gapid.proto.service.path.Path; +import com.google.gapid.util.Experimental; import com.google.gapid.views.CommandTree; -import com.google.gapid.views.ContextSelector; +import com.google.gapid.views.DeviceDialog; import com.google.gapid.views.FramebufferView; import com.google.gapid.views.GeometryView; import com.google.gapid.views.LogView; import com.google.gapid.views.MemoryView; import com.google.gapid.views.PipelineView; +import com.google.gapid.views.ProfileView; import com.google.gapid.views.ReportView; +import com.google.gapid.views.ShaderList; import com.google.gapid.views.ShaderView; import com.google.gapid.views.StateView; import com.google.gapid.views.Tab; +import com.google.gapid.views.TextureList; import com.google.gapid.views.TextureView; -import com.google.gapid.views.ThumbnailScrubber; import com.google.gapid.widgets.TabArea; import com.google.gapid.widgets.TabArea.FolderInfo; import com.google.gapid.widgets.TabArea.Persistance; +import com.google.gapid.widgets.TabComposite; +import com.google.gapid.widgets.TabComposite.TabContent; import com.google.gapid.widgets.TabComposite.TabInfo; import com.google.gapid.widgets.Widgets; @@ -53,11 +64,11 @@ import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; @@ -65,23 +76,25 @@ /** * Main view shown when a graphics trace is loaded. */ -public class GraphicsTraceView extends Composite implements MainWindow.MainView { +public class GraphicsTraceView extends Composite + implements MainWindow.MainView, Resources.Listener, Follower.Listener { private final Models models; private final Widgets widgets; + private final Map typeActions; protected final Set hiddenTabs; - protected TabArea tabs; + protected final TabArea tabs; public GraphicsTraceView(Composite parent, Models models, Widgets widgets) { super(parent, SWT.NONE); this.models = models; this.widgets = widgets; + this.typeActions = Maps.newHashMap(); this.hiddenTabs = getHiddenTabs(models.settings); - setLayout(new GridLayout(1, false)); + new DeviceDialog(this, models, widgets); - new ContextSelector(this, models) - .setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + setLayout(new GridLayout(1, false)); tabs = new TabArea(this, models.analytics, widgets.theme, new Persistance() { @Override @@ -97,16 +110,36 @@ public TabArea.FolderInfo[] restore() { tabs.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - models.follower.addListener(new Follower.Listener() { + TabComposite.Listener listener = new TabComposite.Listener() { @Override - public void onMemoryFollowed(Path.Memory path) { - tabs.showTab(MainTab.Type.Memory); + public void onTabCreated(TabInfo tab) { + if (tab.id instanceof MainTab.Type) { + syncTabMenuItem((MainTab.Type)tab.id, true); + } } @Override - public void onStateFollowed(Path.Any path) { - tabs.showTab(MainTab.Type.ApiState); + public void onTabClosed(TabInfo tab) { + if (tab.id instanceof MainTab.Type) { + syncTabMenuItem((MainTab.Type)tab.id, false); + } } + + @Override + public void onTabPinned(TabInfo tab) { + if (tab.id instanceof MainTab.Type) { + syncTabMenuItem((MainTab.Type)tab.id, false); + } + } + }; + tabs.addListener(listener); + + models.resources.addListener(this); + models.follower.addListener(this); + addListener(SWT.Dispose, e -> { + tabs.removeListener(listener); + models.resources.removeListener(this); + models.follower.removeListener(this); }); } @@ -128,44 +161,128 @@ public void updateViewMenu(MenuManager manager) { manager.add(createViewTabsMenu()); } + @Override + public void onShaderSelected(Service.Resource shader) { + if (shader != null) { + showTab(MainTab.Type.Shaders); + } + } + + @Override + public void onShaderPinned(Resource shader) { + if (!tabs.showTab(shader)) { + String label = shader.getHandle(); + if (!shader.getLabel().isEmpty()) { + label = "Shader<" + shader.getLabel() + ">"; + } + TabInfo tabInfo = new TabInfo(shader, Analytics.View.ShaderView, label, parent -> { + Tab tab = new ShaderView(parent, shader, models, widgets); + tab.reinitialize(); + return tab; + }); + tabs.addTabToLargestFolder(tabInfo); + tabs.showTab(shader); + } + } + + @Override + public void onTextureSelected(Service.Resource texture) { + if (texture != null) { + showTab(MainTab.Type.Textures); + } + } + + @Override + public void onTexturePinned(Resource texture) { + if (!tabs.showTab(texture)) { + String label = texture.getHandle(); + if (!texture.getLabel().isEmpty()) { + label = "Image<" + texture.getLabel() + ">"; + } + TabInfo tabInfo = new TabInfo(texture, Analytics.View.TextureView, label, parent -> { + Tab tab = new TextureView(parent, texture, models, widgets); + tab.reinitialize(); + return tab; + }); + tabs.addTabToLargestFolder(tabInfo); + tabs.showTab(texture); + } + } + + @Override + public void onMemoryFollowed(Path.Memory path) { + tabs.showTab(MainTab.Type.Memory); + } + + @Override + public void onStateFollowed(Path.Any path) { + tabs.showTab(MainTab.Type.ApiState); + } + + @Override + public void onFramebufferAttachmentFollowed(Path.FramebufferAttachment path) { + tabs.showTab(MainTab.Type.Framebuffer); + } + + private void showTab(MainTab.Type type) { + if (!tabs.showTab(type)) { + TabInfo tabInfo = new MainTab(type, parent -> { + Tab tab = type.factory.create(parent, models, widgets); + tab.reinitialize(); + return tab; + }); + if (type.position == MainTab.DefaultPosition.Top) { + tabs.addTabToFirstFolder(tabInfo); + } else { + tabs.addTabToLargestFolder(tabInfo); + } + tabs.showTab(type); + } + } + private MenuManager createViewTabsMenu() { MenuManager manager = new MenuManager("&Tabs"); for (MainTab.Type type : MainTab.Type.values()) { + // TODO(b/188416598): Improve report quality and enable the report tab again. + if (type == MainTab.Type.Report && !Experimental.enableUnstableFeatures(models.settings)) { + continue; + } Action action = type.createAction(shown -> { - models.analytics.postInteraction( - type.view, shown ? ClientAction.Enable : ClientAction.Disable); if (shown) { - TabInfo tabInfo = new MainTab(type, parent -> { - Tab tab = type.factory.create(parent, models, widgets); - tab.reinitialize(); - return tab.getControl(); - }); - if (type.top) { - tabs.addTabToFirstFolder(tabInfo); - } else { - tabs.addTabToLargestFolder(tabInfo); - } - tabs.showTab(type); - hiddenTabs.remove(type); + showTab(type); } else { tabs.disposeTab(type); - hiddenTabs.add(type); } - models.settings.writeTabs() - .clearHidden() - .addAllHidden(hiddenTabs.stream().map(MainTab.Type::name).collect(toList())); }); action.setChecked(!hiddenTabs.contains(type)); manager.add(action); + typeActions.put(type, action); } return manager; } + protected void syncTabMenuItem(MainTab.Type type, boolean shown) { + Action action = typeActions.get(type); + if (action != null) { + action.setChecked(shown); + } + if (hiddenTabs.contains(type) == shown) { + if (shown) { + hiddenTabs.remove(type); + } else { + hiddenTabs.add(type); + } + models.settings.writeTabs() + .clearHidden() + .addAllHidden(hiddenTabs.stream().map(MainTab.Type::name).collect(toList())); + } + } + /** * Information about the tabs to be shown in the main window. */ private static class MainTab extends TabInfo { - public MainTab(Type type, Function contentFactory) { + public MainTab(Type type, Function contentFactory) { super(type, type.view, type.label, contentFactory); } @@ -181,6 +298,10 @@ public static FolderInfo[] getFolders(Models models, Widgets widgets, Set SettingsProto.TabsOrBuilder sTabs = models.settings.tabs(); Set allTabs = Sets.newLinkedHashSet(Arrays.asList(Type.values())); allTabs.removeAll(hidden); + // TODO(b/188416598): Improve report quality and enable the report tab again. + if (!Experimental.enableUnstableFeatures(models.settings)) { + allTabs.remove(MainTab.Type.Report); + } Iterator structs = Splitter.on(';') .trimResults() .omitEmptyStrings() @@ -198,13 +319,18 @@ public static FolderInfo[] getFolders(Models models, Widgets widgets, Set } if (!allTabs.isEmpty()) { - TabInfo[] tabsToAdd = new TabInfo[allTabs.size()]; - int i = 0; + List toAddToLargest = Lists.newArrayList(); + List toAddToTop = Lists.newArrayList(); for (Type tab : allTabs) { - tabsToAdd[i++] = new MainTab(tab, - parent -> tab.factory.create(parent, models, widgets).getControl()); + (tab.position == DefaultPosition.Top ? toAddToTop : toAddToLargest).add( + new MainTab(tab, parent -> tab.factory.create(parent, models, widgets))); + } + if (!toAddToLargest.isEmpty()) { + root = root.addToLargest(toAddToLargest.toArray(new TabInfo[toAddToLargest.size()])); + } + if (!toAddToTop.isEmpty()) { + root = root.addToFirst(toAddToTop.toArray(new TabInfo[toAddToTop.size()])); } - root = root.addToLargest(tabsToAdd); } return root.children; } @@ -248,50 +374,30 @@ private static FolderInfo parse(Models models, Widgets widgets, Iterator private static FolderInfo[] getDefaultFolderInfo( Models models, Widgets widgets, Set hidden) { - Set allTabs = Sets.newLinkedHashSet(Arrays.asList(Type.values())); - allTabs.removeAll(hidden); - boolean hasFilmStrip = allTabs.remove(Type.Filmstrip); - List folders = Lists.newArrayList(); - if (allTabs.contains(Type.ApiCalls)) { - folders.add(new FolderInfo(new TabInfo[] { - new MainTab(Type.ApiCalls, - parent -> Type.ApiCalls.factory.create(parent, models, widgets).getControl()) - }, 1)); - allTabs.remove(Type.ApiCalls); - } - List center = Lists.newArrayList(); - for (Iterator it = allTabs.iterator(); it.hasNext(); ) { - Type type = it.next(); - if (type == Type.Memory || type == Type.ApiState) { - continue; + ListMultimap toAdd = + MultimapBuilder.enumKeys(DefaultPosition.class).arrayListValues().build(); + for (Type type : Type.values()) { + if (!hidden.contains(type)) { + toAdd.put(type.position.key(), new MainTab( + type, parent -> type.factory.create(parent, models, widgets))); } - center.add(new MainTab( - type, parent -> type.factory.create(parent, models, widgets).getControl())); - it.remove(); } - if (!center.isEmpty()) { - folders.add(new FolderInfo(center.toArray(new TabInfo[center.size()]), 3)); + + List bottom = Lists.newArrayList(); + if (toAdd.containsKey(DefaultPosition.Left)) { + bottom.add(new FolderInfo(toAdd.get(DefaultPosition.Left), 1)); } - if (!allTabs.isEmpty()) { - TabInfo[] right = new TabInfo[allTabs.size()]; - if (allTabs.contains(Type.ApiState)) { - right[0] = new MainTab(Type.ApiState, - parent -> Type.ApiState.factory.create(parent, models, widgets).getControl()); - } - if (allTabs.contains(Type.Memory)) { - right[right.length - 1] = new MainTab(Type.Memory, - parent -> Type.Memory.factory.create(parent, models, widgets).getControl()); - } - folders.add(new FolderInfo(right, 1)); + if (toAdd.containsKey(DefaultPosition.Center)) { + bottom.add(new FolderInfo(toAdd.get(DefaultPosition.Center), 3)); } + if (toAdd.containsKey(DefaultPosition.Right)) { + bottom.add(new FolderInfo(toAdd.get(DefaultPosition.Right), 1)); + } + FolderInfo[] result = bottom.toArray(new FolderInfo[bottom.size()]); - FolderInfo[] result = folders.toArray(new FolderInfo[folders.size()]); - if (hasFilmStrip) { + if (toAdd.containsKey(DefaultPosition.Top)) { result = new FolderInfo[] { - new FolderInfo(new TabInfo[] { - new MainTab(Type.Filmstrip, - parent -> Type.Filmstrip.factory.create(parent, models, widgets).getControl()), - }, 1), + new FolderInfo(toAdd.get(DefaultPosition.Top), 1), new FolderInfo(result, 4), }; } else { @@ -329,21 +435,22 @@ private static void flatten( if (folder.tabs != null) { structure.append('f').append(folder.tabs.length).append(';'); for (TabInfo tab : folder.tabs) { - tabs.add(((Type)tab.id).name()); + if (tab.id instanceof Type) { + tabs.add(((Type)tab.id).name()); + } } } } private static List getTabs( - Iterator names, int count, Set left, Models models, Widgets widgets) { + Iterator names, int count, Set left, Models models, Widgets widgets) { List result = Lists.newArrayList(); for (int i = 0; i < count && names.hasNext(); i++) { try { Type type = Type.valueOf(names.next()); if (left.remove(type)) { - result.add(new MainTab(type, - parent -> type.factory.create(parent, models, widgets).getControl())); + result.add(new MainTab(type, parent -> type.factory.create(parent, models, widgets))); } } catch (IllegalArgumentException e) { // Ignore incorrect names in the properties. @@ -356,30 +463,30 @@ private static List getTabs( * Information about the available tabs. */ public static enum Type { - Filmstrip(View.FilmStrip, "Filmstrip", true, ThumbnailScrubber::new), + Profile(View.Profile, "Profile", DefaultPosition.Top, ProfileView::new), - ApiCalls(View.Commands, "Commands", false, CommandTree::new), + ApiCalls(View.Commands, "Performance", DefaultPosition.Left, CommandTree::new), - Framebuffer(View.Framebuffer, "Framebuffer", false, FramebufferView::new), - Pipeline(View.Pipeline, "Pipeline", false, PipelineView::new), - Textures(View.Textures, "Textures", false, TextureView::new), - Geometry(View.Geometry, "Geometry", false, GeometryView::new), - Shaders(View.Shaders, "Shaders", false, ShaderView::new), - Report(View.Report, "Report", false, ReportView::new), - Log(View.Log, "Log", false, (p, m, w) -> new LogView(p, w)), + Framebuffer(View.Framebuffer, "Framebuffer", DefaultPosition.Center, FramebufferView::new), + Pipeline(View.Pipeline, "Pipeline", DefaultPosition.Center, PipelineView::new), + Textures(View.Textures, "Textures", DefaultPosition.Center, TextureList::new), + Geometry(View.Geometry, "Geometry", DefaultPosition.Center, GeometryView::new), + Shaders(View.Shaders, "Shaders", DefaultPosition.Center, ShaderList::new), + Report(View.Report, "Report", DefaultPosition.Center, ReportView::new), + Log(View.Log, "Log", DefaultPosition.Center, (p, m, w) -> new LogView(p, w)), - ApiState(View.State, "State", false, StateView::new), - Memory(View.Memory, "Memory", false, MemoryView::new); + ApiState(View.State, "State", DefaultPosition.Right, StateView::new), + Memory(View.Memory, "Memory", DefaultPosition.Right, MemoryView::new); public final View view; public final String label; - public final boolean top; + public final DefaultPosition position; public final TabFactory factory; - private Type(View view, String label, boolean top, TabFactory factory) { + private Type(View view, String label, DefaultPosition position, TabFactory factory) { this.view = view; this.label = label; - this.top = top; + this.position = position; this.factory = factory; } @@ -396,6 +503,17 @@ public void run() { } } + public static enum DefaultPosition { + Top, Left, Center, Right; + + // For now, keep the four values, but the new layout is just top, bottom. + // All of the code currently only checks for Top, except for the default layout generator, + // which now uses this function. + public DefaultPosition key() { + return (this == Top) ? this : Left; + } + } + /** * Factory to create the UI components of a tab. */ diff --git a/gapic/src/main/com/google/gapid/LoadingScreen.java b/gapic/src/main/com/google/gapid/LoadingScreen.java index 87fd27eef1..67b5275b19 100644 --- a/gapic/src/main/com/google/gapid/LoadingScreen.java +++ b/gapic/src/main/com/google/gapid/LoadingScreen.java @@ -18,17 +18,25 @@ import static com.google.common.base.Preconditions.checkNotNull; import static com.google.gapid.util.GapidVersion.GAPID_VERSION; import static com.google.gapid.util.GeoUtils.bottomLeft; +import static com.google.gapid.views.TracerDialog.showFrameTracingDialog; import static com.google.gapid.views.TracerDialog.showOpenTraceDialog; +import static com.google.gapid.views.TracerDialog.showSystemTracingDialog; import static com.google.gapid.views.TracerDialog.showTracingDialog; import static com.google.gapid.widgets.Widgets.createComposite; import static com.google.gapid.widgets.Widgets.createLabel; import static com.google.gapid.widgets.Widgets.createMenuItem; +import static com.google.gapid.widgets.Widgets.createSelectableLabel; import static com.google.gapid.widgets.Widgets.filling; import static com.google.gapid.widgets.Widgets.recursiveAddListener; +import static com.google.gapid.widgets.Widgets.recursiveSetBackground; +import static com.google.gapid.widgets.Widgets.recursiveSetForeground; import static com.google.gapid.widgets.Widgets.scheduleIfNotDisposed; import static com.google.gapid.widgets.Widgets.withIndents; import static com.google.gapid.widgets.Widgets.withLayoutData; import static com.google.gapid.widgets.Widgets.withMarginAndSpacing; +import static com.google.gapid.widgets.Widgets.withMarginOnly; +import static com.google.gapid.widgets.Widgets.withSpacing; +import static com.google.gapid.widgets.Widgets.withSpans; import com.google.gapid.models.Analytics.View; import com.google.gapid.models.Models; @@ -42,12 +50,15 @@ import com.google.gapid.widgets.Widgets; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; @@ -62,6 +73,7 @@ public class LoadingScreen extends Composite { private final Theme theme; private final Label statusLabel; + private final Composite buttonContainer; private final Composite optionsContainer; private OptionBar recentBar; private Models models; @@ -71,27 +83,51 @@ public class LoadingScreen extends Composite { public LoadingScreen(Composite parent, Theme theme) { super(parent, SWT.NONE); this.theme = theme; - setLayout(CenteringLayout.goldenRatio()); + setLayout(new GridLayout(1, false)); - Composite container = createComposite(this, new GridLayout(1, false)); - createLabel(container, "", theme.dialogLogo()) - .setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); + Composite logo = + createComposite(this, withMarginOnly(new GridLayout(2, false), 10, 5)); + withLayoutData(createLabel(logo, "", theme.dialogLogoSmall()), + new GridData(SWT.LEFT, SWT.CENTER, true, false)); - Label titleLabel = createLabel(container, Messages.WINDOW_TITLE); - titleLabel.setFont(theme.bigBoldFont()); - titleLabel.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); + String version = "Version " + GAPID_VERSION.toStringWithYear(false); + StyledText title = withLayoutData( + createSelectableLabel(logo, Messages.WINDOW_TITLE + " " + version), + new GridData(SWT.LEFT, SWT.CENTER, true, false)); + title.setStyleRanges(new StyleRange[] { + new StyleRange(0, Messages.WINDOW_TITLE.length() + 2, null, null) {{ + font = theme.bigBoldFont(); + }}, + new StyleRange(Messages.WINDOW_TITLE.length() + 2, version.length(), + theme.welcomeVersionColor(), null), + }); + + statusLabel = withLayoutData(createLabel(logo, "Starting up..."), + withSpans(new GridData(SWT.LEFT, SWT.TOP, true, false), 3, 1)); - Label versionLabel = createLabel(container, "Version " + GAPID_VERSION.toFriendlyString()); - versionLabel.setForeground(theme.welcomeVersionColor()); - versionLabel.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); + Composite center = withLayoutData(createComposite(this, CenteringLayout.goldenRatio()), + new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite container = createComposite(center, withSpacing(new GridLayout(1, false), 0, 125)); - statusLabel = createLabel(container, "Starting up..."); - statusLabel.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); + buttonContainer = withLayoutData( + createComposite(container, withSpacing(new GridLayout(2, false), 150, 5)), + new GridData(SWT.CENTER, SWT.TOP, false, false)); + + withLayoutData(BigButton.systemProfiler(buttonContainer, theme, e -> + showSystemTracingDialog( + checkNotNull(client), getShell(), checkNotNull(models), checkNotNull(widgets))), + new GridData(SWT.FILL, SWT.TOP, true, false)); + withLayoutData(BigButton.frameProfiler(buttonContainer, theme, e -> + showFrameTracingDialog( + checkNotNull(client), getShell(), checkNotNull(models), checkNotNull(widgets))), + new GridData(SWT.FILL, SWT.TOP, true, false)); optionsContainer = createComposite(container, filling(new RowLayout(SWT.VERTICAL), true, true)); optionsContainer.setLayoutData(new GridData(SWT.CENTER, SWT.TOP, true, false)); - createOptions(); + + buttonContainer.setVisible(false); + optionsContainer.setVisible(false); } public void setText(String status) { @@ -108,6 +144,7 @@ public void showOptions(Client newClient, Models newModels, Widgets newWidgets) this.widgets = newWidgets; statusLabel.setVisible(false); + buttonContainer.setVisible(true); optionsContainer.setVisible(true); } @@ -142,8 +179,6 @@ private void createOptions() { OptionBar.simple(optionsContainer, theme.help(), "Help", e -> { AboutDialog.showHelp(checkNotNull(models).analytics); }); - - optionsContainer.setVisible(false); } private static String truncate(String file) { @@ -161,12 +196,59 @@ private static String truncate(String file) { return "..." + file; } + protected static void addListeners(Composite parent, Listener onClick) { + Display display = parent.getDisplay(); + Color fgColor = display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); + Color bgColor = display.getSystemColor(SWT.COLOR_LIST_SELECTION); + recursiveAddListener(parent, SWT.MouseEnter, e -> { + recursiveSetForeground(parent, fgColor); + recursiveSetBackground(parent, bgColor); + }); + recursiveAddListener(parent, SWT.MouseExit, e -> { + recursiveSetForeground(parent, null); + recursiveSetBackground(parent, null); + }); + recursiveAddListener(parent, SWT.MouseUp, onClick); + + parent.setCursor(display.getSystemCursor(SWT.CURSOR_HAND)); + } + + private static class BigButton extends Composite { + private BigButton(Composite parent, Image icon, String label, String toolTip, Listener onClick) { + super(parent, SWT.NONE); + setLayout(withMarginOnly(new GridLayout(1, false), 5, 5)); + setToolTipText(toolTip); + + withLayoutData(createLabel(this, "", icon), + new GridData(SWT.CENTER, SWT.TOP, false, false)) + .setToolTipText(toolTip); + withLayoutData(createLabel(this, label), + new GridData(SWT.CENTER, SWT.BOTTOM, false, false)) + .setToolTipText(toolTip); + + addListeners(this, onClick); + } + + public static BigButton systemProfiler(Composite parent, Theme theme, Listener onClick) { + return new BigButton(parent, theme.systemProfiler(), "Capture System Profiler trace", + "Take a trace of the entire system while your app is running.", onClick); + } + + public static BigButton frameProfiler(Composite parent, Theme theme, Listener onClick) { + return new BigButton(parent, theme.frameProfiler(), "Capture Frame Profiler trace", + "Take a trace of a single frame to profile render passes and draw calls.", onClick); + } + } + private static class OptionBar extends Composite { - private OptionBar(Composite parent, Image icon, String label, Image dropDown, String shortcut) { + private OptionBar(Composite parent, Image icon, String label, Image dropDown, String shortcut, + Listener onClick) { super(parent, SWT.NONE); setLayout(withMarginAndSpacing(new GridLayout(4, false), 10, 2, 0, 0)); - createLabel(this, "", icon); + if (icon != null) { + createLabel(this, "", icon); + } withLayoutData(createLabel(this, label), withIndents(new GridData(SWT.LEFT, SWT.CENTER, false, false), 10, 0)); if (dropDown != null) { @@ -177,38 +259,21 @@ private OptionBar(Composite parent, Image icon, String label, Image dropDown, St withIndents(new GridData(SWT.RIGHT, SWT.CENTER, true, false), 40, 0)); } - setCursor(getDisplay().getSystemCursor(SWT.CURSOR_HAND)); - - Color fgColor = parent.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT); - Color bgColor = parent.getDisplay().getSystemColor(SWT.COLOR_LIST_SELECTION); - recursiveAddListener(this, SWT.MouseEnter, e -> { - Widgets.recursiveSetForeground(this, fgColor); - Widgets.recursiveSetBackground(this, bgColor); - }); - recursiveAddListener(this, SWT.MouseExit, e -> { - Widgets.recursiveSetForeground(this, null); - Widgets.recursiveSetBackground(this, null); - }); + addListeners(this, onClick); } public static OptionBar simple(Composite parent, Image icon, String label, Listener onClick) { - OptionBar bar = new OptionBar(parent, icon, label, null, null); - recursiveAddListener(bar, SWT.MouseUp, onClick); - return bar; + return new OptionBar(parent, icon, label, null, null, onClick); } public static OptionBar withShortcut( Composite parent, Image icon, String label, String shortcut, Listener onClick) { - OptionBar bar = new OptionBar(parent, icon, label, null, shortcut); - recursiveAddListener(bar, SWT.MouseUp, onClick); - return bar; + return new OptionBar(parent, icon, label, null, shortcut, onClick); } public static OptionBar withDropDown( Theme theme, Composite parent, Image icon, String label, Listener onClick) { - OptionBar bar = new OptionBar(parent, icon, label, theme.arrowDropDownLight(), null); - recursiveAddListener(bar, SWT.MouseUp, onClick); - return bar; + return new OptionBar(parent, icon, label, theme.arrowDropDownLight(), null, onClick); } } } diff --git a/gapic/src/main/com/google/gapid/Main.java b/gapic/src/main/com/google/gapid/Main.java index 13f2fabb2c..642b21a17b 100644 --- a/gapic/src/main/com/google/gapid/Main.java +++ b/gapic/src/main/com/google/gapid/Main.java @@ -17,29 +17,36 @@ import static com.google.gapid.util.GapidVersion.GAPID_VERSION; import static com.google.gapid.views.ErrorDialog.showErrorDialog; +import static com.google.gapid.views.ErrorDialog.showErrorDialogWithTwoButtons; import static com.google.gapid.views.WelcomeDialog.showFirstTimeDialog; import static com.google.gapid.widgets.Widgets.scheduleIfNotDisposed; import com.google.common.base.Throwables; import com.google.gapid.Server.GapisInitException; import com.google.gapid.models.Analytics; +import com.google.gapid.models.Devices; import com.google.gapid.models.Follower; import com.google.gapid.models.Models; import com.google.gapid.models.Settings; -import com.google.gapid.perfetto.PerfettoConfig; import com.google.gapid.perfetto.canvas.PanelCanvas; +import com.google.gapid.server.Client; import com.google.gapid.server.GapiPaths; import com.google.gapid.server.GapisProcess; import com.google.gapid.util.Crash2ExceptionHandler; import com.google.gapid.util.ExceptionHandler; +import com.google.gapid.util.Experimental; import com.google.gapid.util.Flags; import com.google.gapid.util.Flags.Flag; import com.google.gapid.util.Logging; +import com.google.gapid.util.MacApplication; import com.google.gapid.util.Messages; +import com.google.gapid.util.OS; import com.google.gapid.util.Scheduler; +import com.google.gapid.views.TracerDialog; import com.google.gapid.widgets.Theme; import com.google.gapid.widgets.Widgets; +import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.window.Window; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; @@ -65,6 +72,10 @@ public static void main(String[] args) throws Exception { Theme theme = Theme.load(Display.getDefault()); ExceptionHandler handler = Crash2ExceptionHandler.register(settings); + if (OS.isMac) { + MacApplication.listenForOpenDocument(Display.getDefault()); + } + try { new UI(settings, theme, handler, args).show(); } finally { @@ -81,8 +92,10 @@ private static class UI implements GapisProcess.Listener { private final ExceptionHandler handler; private final String[] args; protected final MainWindow window; - private final Server server; + // Client is an immutable wrapper keeping track of inner server-dependent GapidClient instance. + private final Client client; + private Server server; private Models models; private Widgets widgets; @@ -98,7 +111,8 @@ public void create() { scheduleIfNotDisposed(getShell(), () -> Scheduler.EXECUTOR.execute(UI.this::startup)); } }; - server = new Server(settings); + this.client = new Client(); + server = new Server(settings, client); registerWindowExceptionHandler(); } @@ -111,7 +125,13 @@ private void registerWindowExceptionHandler() { LOG.log(Level.WARNING, "Unhandled exception in the UI thread.", thrown); handler.reportException(thrown); - showErrorDialog(null, getAnalytics(), "Unhandled exception in the UI thread.", thrown); + + if (shouldSuppressUIErrorDialog(thrown)) { + LOG.log(Level.WARNING, "*** Suppressed Exception Display in UI as known bug ***"); + } + else { + showErrorDialog(null, getAnalytics(), "Unhandled exception in the UI thread.", thrown); + } }); } @@ -141,8 +161,8 @@ protected void startup() { } private void uiStartup(Shell shell) { - models = Models.create(shell, settings, handler, server.getClient(), window.getStatusBar()); - widgets = Widgets.create(shell.getDisplay(), theme, server.getClient(), models); + models = Models.create(shell, settings, handler, client, window.getStatusBar()); + widgets = Widgets.create(shell.getDisplay(), theme, client, models); Runnable onStart = () -> { if (args.length == 1) { @@ -150,7 +170,7 @@ private void uiStartup(Shell shell) { } }; - window.initMainUi(server.getClient(), models, widgets); + window.initMainUi(client, models, widgets); if (models.settings.preferences().getSkipFirstRunDialog()) { shell.getDisplay().asyncExec(onStart); } else { @@ -158,7 +178,7 @@ private void uiStartup(Shell shell) { } // Add the links on Loading Screen after the server set up. - window.updateLoadingScreen(server.getClient(), models, widgets); + window.updateLoadingScreen(client, models, widgets); } @Override @@ -168,13 +188,28 @@ public void onStatus(String message) { @Override public void onServerExit(int code, String panic) { - scheduleIfStillOpen(shell -> - // TODO: try to restart the server? - showErrorDialog(shell, getAnalytics(), - "The gapis server has exited with an error code of: " + code, panic) + scheduleIfStillOpen(shell -> showErrorDialogWithTwoButtons( + shell, getAnalytics(), String.format(Messages.SERVER_ERROR_MESSAGE, code), panic, + IDialogConstants.RETRY_ID, "Restart Server", this::restartServer, + IDialogConstants.CLOSE_ID, "Exit", window::close) ); } + private void restartServer() { + try { + server = new Server(settings, client); + server.connect(this); + scheduleIfStillOpen(this::restartUi); + } catch (GapisInitException e) { + onServerExit(-42, Throwables.getStackTraceAsString(e)); + } + } + + private void restartUi(Shell shell) { + models.reset(); + window.showWelcomeScreen(); + } + private void scheduleIfStillOpen(ShellRunnable run) { Shell shell = window.getShell(); if (shell == null) { @@ -187,6 +222,25 @@ private Analytics getAnalytics() { return (models == null) ? null : models.analytics; } + private boolean shouldSuppressUIErrorDialog(Throwable throwable) { + if (OS.isMac && throwable != null) { + + // TODO b/178397207: Disable error dialog for a known UI issue, while waiting for asolution from the SWT side. + boolean isDrawRectBug = throwable instanceof NullPointerException && + throwable.getStackTrace().length > 0 && + "org.eclipse.swt.widgets.Widget".equals(throwable.getStackTrace()[0].getClassName()) && + "drawRect".equals(throwable.getStackTrace()[0].getMethodName()); + + // TODO b/216617761: Disabe error dialog for a different known UI issue, while waiting for a solution from the SWT side. + boolean isUnknownNullExceptionBug = throwable instanceof NullPointerException && + throwable.getStackTrace().length == 0; + + return isDrawRectBug || isUnknownNullExceptionBug; + } + + return false; + } + private static interface ShellRunnable { public void run(Shell shell); } @@ -196,6 +250,10 @@ private static interface ShellRunnable { Flags.help, Flags.fullHelp, Flags.version, + Devices.skipDeviceValidation, + Experimental.enableAll, + Experimental.enableProfileExperiments, + Experimental.enableUnstableFeatures, GapiPaths.gapidPath, GapiPaths.adbPath, GapisProcess.disableGapisTimeout, @@ -209,7 +267,9 @@ private static interface ShellRunnable { Logging.logDir, Follower.logFollowRequests, Server.useCache, - PerfettoConfig.perfettoConfig, PanelCanvas.showRedraws, + TracerDialog.maxFrames, + TracerDialog.maxPerfetto, + TracerDialog.enableLoadValidationLayer, }; } diff --git a/gapic/src/main/com/google/gapid/MainWindow.java b/gapic/src/main/com/google/gapid/MainWindow.java index 094fa80b2f..741a60b87e 100644 --- a/gapic/src/main/com/google/gapid/MainWindow.java +++ b/gapic/src/main/com/google/gapid/MainWindow.java @@ -81,6 +81,7 @@ public class MainWindow extends ApplicationWindow { private final Settings settings; private final Theme theme; private Composite mainArea; + private LoadablePanel mainUi; private LoadingScreen loadingScreen; protected StatusBar statusBar; @@ -105,13 +106,14 @@ public void updateLoadingScreen(Client client, Models models, Widgets widgets) { loadingScreen.showOptions(client, models, widgets); } + public void initMainUi(Client client, Models models, Widgets widgets) { Shell shell = getShell(); showLoadingMessage("Setting up UI..."); initMenus(client, models, widgets); - LoadablePanel mainUi = new LoadablePanel( + mainUi = new LoadablePanel( mainArea, widgets, parent -> new MainViewContainer(parent, models, widgets)); models.capture.addListener(new Capture.Listener() { @Override @@ -126,11 +128,15 @@ public void onCaptureLoaded(Message error) { if (error != null) { mainUi.showMessage(error); } else { - MainView view = mainUi.getContents().updateAndGet( - models.capture.getData().capture.getType()); - view.updateViewMenu(findMenu(MenuItems.VIEW_ID)); - getMenuBarManager().updateAll(true); - mainUi.stopLoading(); + // Let all other handlers of this event get a chance to process before we start disposing + // UI components underneath everybody. + scheduleIfNotDisposed(mainUi, () -> { + MainView view = mainUi.getContents().updateAndGet( + models.capture.getData().capture.getType()); + view.updateViewMenu(findMenu(MenuItems.VIEW_ID)); + getMenuBarManager().updateAll(true); + mainUi.stopLoading(); + }); } } }); @@ -147,7 +153,7 @@ public void onCaptureLoaded(Message error) { //schedules a periodic check to see if we should check for updates and if so, checks. showLoadingMessage("Watching for updates..."); } - watchForUpdates(client, models); + models.updateWatcher.watchForUpdates(); showLoadingMessage("Tracking server status..."); trackServerStatus(client); @@ -178,16 +184,6 @@ public void handleEvent(Event event) { }); } - private void watchForUpdates(Client client, Models models) { - new UpdateWatcher(models.settings, client, (release) -> { - scheduleIfNotDisposed(statusBar, () -> { - statusBar.setNotification("New update available", () -> { - Program.launch(release.getBrowserUrl()); - }); - }); - }); - } - private void trackServerStatus(Client client) { new StatusWatcher(client, new StatusWatcher.Listener() { @Override @@ -263,11 +259,17 @@ private Point getDefaultInitialLocation(Point size) { Math.max(0, bounds.height - size.y) / 3); } + public void showWelcomeScreen() { + if (loadingScreen != null) { + setTopControl(loadingScreen); + } + } + @Override protected MenuManager createMenuManager() { MenuManager manager = new MenuManager(); - // Add a dummy file menu, so the UI doesn't move once the rest of the menus are created. + // Add a placeholder file menu, so the UI doesn't move once the rest of the menus are created. MenuManager file = new MenuManager("&File", MenuItems.FILE_ID); file.add(MenuItems.FileExit.create(this::close)); manager.add(file); @@ -336,7 +338,7 @@ public void run() { } private MenuManager createEditMenu(Models models, Widgets widgets) { - MenuManager manager = new MenuManager("&Edit"); + MenuManager manager = new MenuManager("&Edit", MenuItems.EDIT_ID); Action editCopy = MenuItems.EditCopy.create(() -> { models.analytics.postInteraction(View.Main, ClientAction.Copy); widgets.copypaste.doCopy(); @@ -358,7 +360,7 @@ public void onCopyEnabled(boolean enabled) { } private MenuManager createGotoMenu(Models models) { - MenuManager manager = new MenuManager("&Goto"); + MenuManager manager = new MenuManager("&Goto", MenuItems.GOTO_ID); Action gotoCommand = MenuItems.GotoCommand.create(() -> showGotoCommandDialog(getShell(), models)); Action gotoMemory = MenuItems.GotoMemory.create(() -> showGotoMemoryDialog(getShell(), models)); @@ -396,13 +398,15 @@ private static MenuManager createViewMenu() { } private MenuManager createHelpMenu(Models models, Widgets widgets) { - MenuManager manager = new MenuManager("&Help"); + MenuManager manager = new MenuManager("&Help", MenuItems.HELP_ID); manager.add(MenuItems.HelpOnlineHelp.create(() -> showHelp(models.analytics))); manager.add(MenuItems.HelpAbout.create( () -> showAbout(getShell(), models.analytics, widgets))); - manager.add(MenuItems.HelpShowLogs.create(() -> showLogDir(models.analytics))); + manager.add(MenuItems.HelpUpdateCheck.create( + () -> UpdateWatcher.manualUpdateCheck(getShell(), models))); manager.add(MenuItems.HelpLicenses.create( () -> showLicensesDialog(getShell(), models.analytics, widgets.theme))); + manager.add(MenuItems.HelpShowLogs.create(() -> showLogDir(models.analytics))); manager.add(MenuItems.HelpFileBug.create( () -> Program.launch(URLs.FILE_BUG_URL))); return manager; @@ -479,12 +483,16 @@ public static enum MenuItems { HelpOnlineHelp("&Online Help\tF1", SWT.F1), HelpAbout("&About"), - HelpShowLogs("Open &Log Directory"), + HelpUpdateCheck("&Check for Updates"), HelpLicenses("&Licenses"), + HelpShowLogs("Open &Log Directory"), HelpFileBug("File a &Bug"); public static final String FILE_ID = "file"; + public static final String EDIT_ID = "edit"; + public static final String GOTO_ID = "goto"; public static final String VIEW_ID = "view"; + public static final String HELP_ID = "help"; private final String label; private final int accelerator; diff --git a/gapic/src/main/com/google/gapid/Server.java b/gapic/src/main/com/google/gapid/Server.java index 98b24f6bf8..379b6e2640 100644 --- a/gapic/src/main/com/google/gapid/Server.java +++ b/gapic/src/main/com/google/gapid/Server.java @@ -60,11 +60,12 @@ public class Server { "cache", true, "Whether to use a cache between the UI and the gapis server.", true); private final Settings settings; + private final Client client; private GapisConnection gapisConnection; - private Client client; - public Server(Settings settings) { + public Server(Settings settings, Client client) { this.settings = settings; + this.client = client; } public void connect(GapisProcess.Listener listener) throws GapisInitException { @@ -86,10 +87,6 @@ public void connect(GapisProcess.Listener listener) throws GapisInitException { } } - public Client getClient() { - return client; - } - public void disconnect() { if (gapisConnection != null) { gapisConnection.close(); @@ -107,7 +104,7 @@ private void connectToServer(GapisProcess.Listener listener) throws GapisInitExc if (!useCache.get()) { LOG.log(WARNING, "** Not using caching in the UI, this is only meant for testing. **"); } - client = new Client(connection.createGapidClient(useCache.get())); + client.setGapidClient(connection.createGapidClient(useCache.get())); } catch (IOException e) { throw new GapisInitException( GapisInitException.MESSAGE_FAILED_CONNECT, "unable to create client", e); diff --git a/gapic/src/main/com/google/gapid/glviewer/Geometry.java b/gapic/src/main/com/google/gapid/glviewer/Geometry.java index 485c177af7..db150dde16 100644 --- a/gapic/src/main/com/google/gapid/glviewer/Geometry.java +++ b/gapic/src/main/com/google/gapid/glviewer/Geometry.java @@ -31,16 +31,17 @@ * point cloud, wire mesh, or solid. */ public class Geometry { - public static final Geometry NULL = new Geometry(null, false); + public static final Geometry NULL = new Geometry(null, Orientation.Y_UP); public final Model model; - public final boolean zUp; + public final Orientation orientation; public final MatD modelMatrix; - public Geometry(Model model, boolean zUp) { + public Geometry(Model model, Orientation orientation) { this.model = model; - this.zUp = zUp; - this.modelMatrix = getBounds().getCenteringMatrix(Constants.SCENE_SCALE_FACTOR, zUp); + this.orientation = orientation; + this.modelMatrix = getBounds().getCenteringMatrix( + Constants.SCENE_SCALE_FACTOR, orientation.isZ(), orientation.isDown()); } public BoundingBox getBounds() { @@ -180,4 +181,16 @@ public static enum DisplayMode { this.glPolygonMode = glPolygonMode; } } + + public static enum Orientation { + Y_UP, Y_DOWN, Z_UP, Z_DOWN; + + public boolean isDown() { + return this == Y_DOWN || this == Z_DOWN; + } + + public boolean isZ() { + return this == Z_UP || this == Z_DOWN; + } + } } diff --git a/gapic/src/main/com/google/gapid/glviewer/geo/BoundingBox.java b/gapic/src/main/com/google/gapid/glviewer/geo/BoundingBox.java index ee03c5e8c0..7704f6679c 100644 --- a/gapic/src/main/com/google/gapid/glviewer/geo/BoundingBox.java +++ b/gapic/src/main/com/google/gapid/glviewer/geo/BoundingBox.java @@ -15,8 +15,6 @@ */ package com.google.gapid.glviewer.geo; -import static com.google.gapid.glviewer.vec.MatD.makeScaleTranslationZupToYup; - import com.google.gapid.glviewer.vec.MatD; import com.google.gapid.glviewer.vec.VecD; @@ -51,15 +49,17 @@ public void add(double x, double y, double z) { /** * @return a matrix that will center the model at the origin and scale it to the given size. */ - public MatD getCenteringMatrix(double diagonalSize, boolean zUp) { + public MatD getCenteringMatrix(double diagonalSize, boolean zUp, boolean flipUpAxis) { VecD minV = VecD.fromArray(min), maxV = VecD.fromArray(max); double diagonal = maxV.distance(minV); VecD translation = maxV.subtract(minV).multiply(0.5f).add(minV).multiply(-1); double scale = (diagonal == 0) ? 1 : diagonalSize / diagonal; - return zUp ? makeScaleTranslationZupToYup(scale, translation) : - MatD.makeScaleTranslation(scale, translation); + MatD flipMatrix = flipUpAxis ? MatD.makeScale(1, -1, 1) : MatD.IDENTITY; + + return zUp ? flipMatrix.multiply(MatD.makeScaleTranslationZupToYup(scale, translation)) : + flipMatrix.multiply(MatD.makeScaleTranslation(scale, translation)); } public BoundingBox transform(MatD transform) { diff --git a/gapic/src/main/com/google/gapid/glviewer/gl/Renderer.java b/gapic/src/main/com/google/gapid/glviewer/gl/Renderer.java index f3364fbbe0..fc66131303 100644 --- a/gapic/src/main/com/google/gapid/glviewer/gl/Renderer.java +++ b/gapic/src/main/com/google/gapid/glviewer/gl/Renderer.java @@ -23,7 +23,6 @@ import com.google.gapid.glviewer.vec.VecD; import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.internal.DPIUtil; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL30; @@ -77,11 +76,11 @@ public void terminate() { * @param width of the back-buffer in real pixels. * @param height of the back-buffer in real pixels. */ - public void setSize(int width, int height) { - physicalWidth = width; - physicalHeight = height; - dipWidth = DPIUtil.autoScaleDown(width); - dipHeight = DPIUtil.autoScaleDown(height); + public void setSize(int width, int height, float factor) { + physicalWidth = (int)(width * factor); + physicalHeight = (int)(height * factor); + dipWidth = width; + dipHeight = height; GL11.glViewport(0, 0, physicalWidth, physicalHeight); } diff --git a/gapic/src/main/com/google/gapid/image/FetchedImage.java b/gapic/src/main/com/google/gapid/image/FetchedImage.java index efd3d12714..887e80b5b5 100644 --- a/gapic/src/main/com/google/gapid/image/FetchedImage.java +++ b/gapic/src/main/com/google/gapid/image/FetchedImage.java @@ -60,6 +60,16 @@ public static ListenableFuture load( }); } + public static ListenableFuture load( + Client client, Path.Device device, Path.ImageInfo imagePath, Consumer onInfo) { + return MoreFutures.transformAsync(client.get(imageInfo(imagePath), device), value -> { + Images.Format format = getFormat(value.getImageInfo()); + onInfo.accept(value.getImageInfo()); + return MoreFutures.transform(client.get(imageData(imagePath, format.format), device), + pixelValue -> new FetchedImage(client, device, format, pixelValue.getImageInfo())); + }); +} + public static ListenableFuture load( Client client, Path.Device device, Path.ResourceData imagePath) { return MoreFutures.transformAsync(client.get(resourceInfo(imagePath), device), value -> { @@ -128,6 +138,12 @@ public static ListenableFuture loadThumbnail( }), 0, 0); } + public static ListenableFuture loadThumbnail( + Client client, Path.Device device, com.google.gapid.proto.image.Image.Info info) { + return loadImage( + immediateFuture(new FetchedImage(client, device, Images.Format.Color8, info)), 0, 0); + } + private static Images.Format getFormat(Info imageInfo) { return Images.Format.from(imageInfo.getFormat()); } diff --git a/gapic/src/main/com/google/gapid/image/Histogram.java b/gapic/src/main/com/google/gapid/image/Histogram.java index 90096e901b..3af1d67c39 100644 --- a/gapic/src/main/com/google/gapid/image/Histogram.java +++ b/gapic/src/main/com/google/gapid/image/Histogram.java @@ -18,12 +18,15 @@ import static java.util.Arrays.stream; import static java.util.stream.Collectors.toSet; +import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.gapid.image.Image.PixelInfo; import com.google.gapid.proto.stream.Stream; import com.google.gapid.proto.stream.Stream.Channel; import com.google.gapid.util.Range; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.stream.DoubleStream; import java.util.stream.IntStream; @@ -87,13 +90,25 @@ public Range getInitialRange(double snapThreshold) { /** * @param percentile the percentile value ranging from 0 to 100. - * @param high if true, return the upper limit on the percentile's bin, otherwise the lower limit. + * @param lastBin if true, for all the percentile bins from different channels, choose the last + * bin, otherwise choose the first bin. * @return the absolute pixel value at the specified percentile in the histogram. */ - private double getPercentile(int percentile, boolean high) { - int bin = bins.getPercentileBin(percentile, channels); - return (bin < 0) ? mapper.limits.max : - getValueFromNormalizedX((bin + (high ? 1 : 0)) / (double)bins.count()); + private double getPercentile(int percentile, boolean lastBin) { + List percentileBins = Lists.newArrayList(); + for (Stream.Channel c : channels) { + int bin = bins.getPercentileBin(percentile, c); + if (bin >= 0) { + percentileBins.add(bin); + } + } + if (percentileBins.isEmpty()) { + return mapper.limits.max; + } else { + int chosenBin = lastBin ? Collections.max(percentileBins) : Collections.min(percentileBins); + // If to choose lastBin, return the upper limit of the bin, otherwise the lower limit. + return getValueFromNormalizedX((chosenBin + (lastBin ? 1 : 0)) / (double)bins.count()); + } } /** @@ -353,21 +368,14 @@ public int count() { /** * Returns the index of the bin which matches the given percentile, or -1. */ - public int getPercentileBin(int percentile, Set channels) { - int highestCount = 0; - for (Stream.Channel c : channels) { - highestCount = Math.max(highestCount, total[getChannelIdx(c)]); - } - - int threshold = percentile * highestCount / 100; - int[] sum = new int[Stream.Channel.values().length]; + public int getPercentileBin(int percentile, Stream.Channel channel) { + int cIdx = getChannelIdx(channel); + int threshold = percentile * total[getChannelIdx(channel)] / 100; + int sum = 0; for (int b = 0; b < bins.length; b++) { - for (Stream.Channel c : channels) { - int cIdx = getChannelIdx(c); - int s = sum[cIdx] += bins[b][cIdx]; - if (s >= threshold) { - return b; - } + sum += bins[b][cIdx]; + if (sum >= threshold) { + return b; } } return -1; diff --git a/gapic/src/main/com/google/gapid/image/Images.java b/gapic/src/main/com/google/gapid/image/Images.java index 3eb2ea507c..0ba59ea4d7 100644 --- a/gapic/src/main/com/google/gapid/image/Images.java +++ b/gapic/src/main/com/google/gapid/image/Images.java @@ -18,6 +18,9 @@ import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.proto.image.Image; +import com.google.gapid.proto.image.Image.FmtETC2.AlphaMode; +import com.google.gapid.proto.image.Image.FmtETC2.ColorMode; +import com.google.gapid.proto.image.Image.Format.FormatCase; import com.google.gapid.proto.stream.Stream; import com.google.gapid.util.MoreFutures; import com.google.gapid.util.Streams; @@ -39,21 +42,27 @@ public class Images { public static final Image.Format FMT_RGBA_U8_NORM = Image.Format.newBuilder() .setUncompressed(Image.FmtUncompressed.newBuilder().setFormat(Streams.FMT_RGBA_U8_NORM)) + .setName("FMT_RGBA_U8_NORM") .build(); public static final Image.Format FMT_DEPTH_U8_NORM = Image.Format.newBuilder() .setUncompressed(Image.FmtUncompressed.newBuilder().setFormat(Streams.FMT_DEPTH_U8_NORM)) + .setName("FMT_DEPTH_U8_NORM") .build(); public static final Image.Format FMT_RGBA_FLOAT = Image.Format.newBuilder() .setUncompressed(Image.FmtUncompressed.newBuilder().setFormat(Streams.FMT_RGBA_FLOAT)) + .setName("FMT_RGBA_FLOAT") .build(); public static final Image.Format FMT_LUMINANCE_FLOAT = Image.Format.newBuilder() .setUncompressed(Image.FmtUncompressed.newBuilder().setFormat(Streams.FMT_LUMINANCE_FLOAT)) + .setName("FMT_LUMINANCE_FLOAT") .build(); public static final Image.Format FMT_DEPTH_FLOAT = Image.Format.newBuilder() .setUncompressed(Image.FmtUncompressed.newBuilder().setFormat(Streams.FMT_DEPTH_FLOAT)) + .setName("FMT_DEPTH_FLOAT") .build(); public static final Image.Format FMT_COUNT_U8 = Image.Format.newBuilder() .setUncompressed(Image.FmtUncompressed.newBuilder().setFormat(Streams.FMT_COUNT_U8)) + .setName("FMT_COUNT_U8") .build(); public static final Set COLOR_CHANNELS = Sets.immutableEnumSet( @@ -131,38 +140,19 @@ public static ListenableFuture noAlpha(ListenableFuture im }); } - public static Image.Format getFormatToRequest(Image.Format format) { - if (isColorFormat(format)) { - return are8BitsEnough(format, COLOR_CHANNELS) ? FMT_RGBA_U8_NORM : - (getChannelCount(format, COLOR_CHANNELS) == 1 ? FMT_LUMINANCE_FLOAT : FMT_RGBA_FLOAT); - } else if (isCountFormat(format)) { - // Currently only one count format - return FMT_COUNT_U8; - } else { - return are8BitsEnough(format, DEPTH_CHANNELS) ? FMT_DEPTH_U8_NORM : FMT_DEPTH_FLOAT; - } - } - public static int getChannelCount(Image.Format format, Set interestedChannels) { switch (format.getFormatCase()) { case UNCOMPRESSED: return getChannelCount(format.getUncompressed().getFormat(), interestedChannels); - case ETC2_R_U11_NORM: - case ETC2_R_S11_NORM: - return 1; - case ETC2_RG_U11_NORM: - case ETC2_RG_S11_NORM: - return 2; + case ETC2: + return getEtc2ChannelCount(format.getEtc2()); case ATC_RGB_AMD: case ETC1_RGB_U8_NORM: - case ETC2_RGB_U8_NORM: case S3_DXT1_RGB: return 3; case ASTC: case ATC_RGBA_EXPLICIT_ALPHA_AMD: case ATC_RGBA_INTERPOLATED_ALPHA_AMD: - case ETC2_RGBA_U8_NORM: - case ETC2_RGBA_U8U8U8U1_NORM: case PNG: case S3_DXT1_RGBA: case S3_DXT3_RGBA: @@ -173,6 +163,25 @@ public static int getChannelCount(Image.Format format, Set inter } } + private static int getEtc2ChannelCount(Image.FmtETC2 format) { + switch(format.getColorMode()) { + case R: + case R_SIGNED: + return 1; + case RG: + case RG_SIGNED: + return 2; + case RGB: + case SRGB: + if (format.getAlphaMode() == AlphaMode.ALPHA_NONE) { + return 3; + } else { + return 4; + } + default: + throw new AssertionError(); + } + } public static int getChannelCount(Stream.Format format, Set interestedChannels) { int result = 0; for (Stream.Component c : format.getComponentsList()) { @@ -185,13 +194,18 @@ public static int getChannelCount(Stream.Format format, Set inte public static boolean are8BitsEnough( Image.Format format, Set interestedChannels) { - switch (format.getFormatCase()) { - case UNCOMPRESSED: - return are8BitsEnough(format.getUncompressed().getFormat(), interestedChannels); - default: - // All Compressed formats can fully be represented as 8 bits (at this time). - return true; + if(format.getFormatCase() == FormatCase.UNCOMPRESSED) { + return are8BitsEnough(format.getUncompressed().getFormat(), interestedChannels); + } + + // Only RGB and SRGB formats of ETC2 can be represented with 8 bits. + if(format.getFormatCase() == FormatCase.ETC2) { + ColorMode colorMode = format.getEtc2().getColorMode(); + return colorMode == ColorMode.RGB || colorMode == ColorMode.SRGB; } + + // All Compressed formats except ETC2 can fully be represented as 8 bits (at this time). + return true; } public static boolean are8BitsEnough( diff --git a/gapic/src/main/com/google/gapid/models/Analytics.java b/gapic/src/main/com/google/gapid/models/Analytics.java index a7f6ceec64..3fae8ac53d 100644 --- a/gapic/src/main/com/google/gapid/models/Analytics.java +++ b/gapic/src/main/com/google/gapid/models/Analytics.java @@ -31,11 +31,12 @@ public class Analytics implements ExceptionHandler { private static final Logger LOG = Logger.getLogger(Analytics.class.getName()); public static enum View { - Main, FilmStrip, LeftTabs, RightTabs, + Main, LeftTabs, RightTabs, About, Help, GotoCommand, GotoMemory, Licenses, Settings, Trace, Welcome, // See MainWindow.MainTab.Type - Commands, Framebuffer, Pipeline, Textures, Geometry, Shaders, Report, Log, State, Memory, - ContextSelector, ReplayDeviceSelector; + FilmStrip, Profile, Commands, Framebuffer, Pipeline, Textures, TextureView, + Geometry, Shaders, ShaderView, Performance, Report, Log, State, Memory, + ContextSelector, ReplayDeviceSelector, QueryMetadata; } private final Client client; diff --git a/gapic/src/main/com/google/gapid/models/ApiContext.java b/gapic/src/main/com/google/gapid/models/ApiContext.java deleted file mode 100644 index 40863e1eb0..0000000000 --- a/gapic/src/main/com/google/gapid/models/ApiContext.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ -package com.google.gapid.models; - -import static com.google.gapid.util.Paths.context; -import static java.util.Arrays.stream; -import static java.util.Comparator.comparingInt; - -import com.google.common.collect.Lists; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.gapid.proto.service.Service; -import com.google.gapid.proto.service.path.Path; -import com.google.gapid.server.Client; -import com.google.gapid.util.Events; -import com.google.gapid.util.MoreFutures; - -import org.eclipse.swt.widgets.Shell; - -import java.util.List; -import java.util.Objects; -import java.util.logging.Logger; - -/** - * Model containing the different API contexts of a capture. - */ -public class ApiContext - extends CaptureDependentModel.ForPath { - private static final Logger LOG = Logger.getLogger(ApiContext.class.getName()); - - private FilteringContext selectedContext = null; - - public ApiContext( - Shell shell, Analytics analytics, Client client, Capture capture, Devices devices) { - super(LOG, shell, analytics, client, Listener.class, capture, devices); - } - - @Override - protected void reset(boolean maintainState) { - super.reset(maintainState); - if (!maintainState) { - selectedContext = null; - } - } - - @Override - protected Path.Any getSource(Capture.Data capture) { - return Path.Any.newBuilder() - .setContexts(Path.Contexts.newBuilder() - .setCapture(capture.path)) - .build(); - } - - @Override - protected boolean shouldLoad(Capture.Data capture) { - return capture != null && capture.isGraphics(); - } - - @Override - protected ListenableFuture doLoad(Path.Any path, Path.Device device) { - return MoreFutures.transform(MoreFutures.transformAsync(client.get(path, device), val -> { - List> contexts = Lists.newArrayList(); - for (Path.Context ctx : val.getContexts().getListList()) { - contexts.add(MoreFutures.transform(client.get(context(ctx), device), - value -> new IdAndContext(ctx, value.getContext()))); - } - return Futures.allAsList(contexts); - }), ctxList -> new Contexts(device, unbox(ctxList))); - } - - private static FilteringContext[] unbox(List contexts) { - if (contexts.isEmpty()) { - return new FilteringContext[0]; - } else if (contexts.size() == 1) { - return new FilteringContext[] { FilteringContext.withoutFilter(contexts.get(0)) }; - } else { - FilteringContext[] result = new FilteringContext[contexts.size() + 1]; - result[0] = FilteringContext.ALL; - for (int i = 0; i < contexts.size(); i++) { - result[i + 1] = new FilteringContext(contexts.get(i)); - } - return result; - } - } - - @Override - protected void fireLoadStartEvent() { - // Do nothing. - } - - @Override - protected void fireLoadedEvent() { - if (count() == 1) { - selectedContext = getData().contexts[0]; - } else if (selectedContext != null) { - selectedContext = stream(getData().contexts) - .filter(c -> c.equals(selectedContext)) - .findFirst() - .orElseGet(this::highestPriorityContext); - } else { - selectedContext = highestPriorityContext(); - } - listeners.fire().onContextsLoaded(); - } - - private FilteringContext highestPriorityContext() { - return stream(getData().contexts) - .max(comparingInt(FilteringContext::getPriority)) - .orElse(FilteringContext.ALL); - } - - public int count() { - return isLoaded() ? getData().contexts.length : 0; - } - - public FilteringContext getSelectedContext() { - return selectedContext != null ? selectedContext : highestPriorityContext(); - } - - public void selectContext(FilteringContext context) { - if (!Objects.equals(context, selectedContext)) { - selectedContext = context; - listeners.fire().onContextSelected(context); - } - } - - public static class Contexts extends DeviceDependentModel.Data { - public final FilteringContext[] contexts; - - public Contexts(Path.Device device, FilteringContext[] contexts) { - super(device); - this.contexts = contexts; - } - } - - /** - * A {@link com.google.gapid.proto.service.Service.Context} wrapper to allow filtering of the - * command tree. - */ - public static class FilteringContext { - public static final FilteringContext ALL = new FilteringContext(null, null) { - @Override - public Path.CommandTree.Builder commandTree(Path.CommandTree.Builder path) { - return path - .setGroupByContext(true) - .setIncludeNoContextGroups(true); - } - - @Override - public Path.Events.Builder events(Path.Events.Builder path) { - return path; - } - - @Override - public Path.Report.Builder report(Path.Report.Builder path) { - return path; - } - - @Override - public String toString() { - return "All contexts"; - } - - @Override - public boolean equals(Object obj) { - return obj == ALL; - } - - @Override - public int hashCode() { - return 0; - } - - @Override - public boolean matches(Path.Context path) { - return true; - } - }; - - private final Path.ID id; - private final Service.Context context; - - public FilteringContext(IdAndContext context) { - this(context.id, context.context); - } - - protected FilteringContext(Path.ID id, Service.Context context) { - this.id = id; - this.context = context; - } - - public static FilteringContext withoutFilter(IdAndContext context) { - return new FilteringContext(context.id, context.context) { - @Override - public Path.CommandTree.Builder commandTree(Path.CommandTree.Builder path) { - return path - .setGroupByFrame(true) - .setGroupByDrawCall(true) - .setGroupByTransformFeedback(true) - .setGroupByUserMarkers(true) - .setGroupBySubmission(true) - .setAllowIncompleteFrame(true); - } - - @Override - public Path.Events.Builder events(Path.Events.Builder path) { - return path; - } - - @Override - public Path.Report.Builder report(Path.Report.Builder path) { - return path; - } - }; - } - - public Path.CommandTree.Builder commandTree(Path.CommandTree.Builder path) { - path.getFilterBuilder().setContext(id); - return path - .setGroupByFrame(true) - .setGroupByDrawCall(true) - .setGroupByTransformFeedback(true) - .setGroupByUserMarkers(true) - .setGroupBySubmission(true) - .setAllowIncompleteFrame(true); - } - - public int getPriority() { - return context != null ? context.getPriority() : 0; - } - - public Path.State.Builder state(Path.State.Builder path) { - if (id != null) { - path.getContextBuilder().setData(id.getData()); - } - return path; - } - - public Path.StateTree.Builder stateTree(Path.StateTree.Builder path) { - if (id != null) { - path.getStateBuilder().getContextBuilder().setData(id.getData()); - } - return path; - } - - public Path.Events.Builder events(Path.Events.Builder path) { - path.getFilterBuilder().setContext(id); - return path; - } - - public Path.Report.Builder report(Path.Report.Builder path) { - path.getFilterBuilder().setContext(id); - return path; - } - - @Override - public String toString() { - return context.getName(); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } else if (obj == ALL || !(obj instanceof FilteringContext)) { - return false; - } - return Objects.equals(id, ((FilteringContext)obj).id); - } - - @Override - public int hashCode() { - return id.hashCode(); - } - - public boolean matches(Path.Context path) { - return Objects.equals(id, path.getID()); - } - } - - @SuppressWarnings("unused") - public static interface Listener extends Events.Listener { - /** - * Event indicating that the contexts have finished loading from the server. - */ - public default void onContextsLoaded() { /* empty */ } - - /** - * Event indicating that the currently selected context has changed. - */ - public default void onContextSelected(FilteringContext context) { /* empty */ } - } - - protected static class IdAndContext { - public final Path.ID id; - public final Service.Context context; - - public IdAndContext(Path.Context path, Service.Context context) { - this.id = path.getID(); - this.context = context; - } - } -} diff --git a/gapic/src/main/com/google/gapid/models/ApiState.java b/gapic/src/main/com/google/gapid/models/ApiState.java index 76c753d8b3..db2b0a65b4 100644 --- a/gapic/src/main/com/google/gapid/models/ApiState.java +++ b/gapic/src/main/com/google/gapid/models/ApiState.java @@ -58,14 +58,14 @@ public class ApiState private final ObjectStore selection = ObjectStore.create(); public ApiState(Shell shell, Analytics analytics, Client client, Devices devices, - Follower follower, CommandStream commands, ApiContext contexts, ConstantSets constants) { + Follower follower, CommandStream commands, ConstantSets constants) { super(LOG, shell, analytics, client, Listener.class, devices); this.constants = constants; commands.addListener(new CommandStream.Listener() { @Override public void onCommandsSelected(CommandIndex index) { - load(stateTree(index, contexts.getSelectedContext()), false); + load(stateTree(index), false); } }); follower.addListener(new Follower.Listener() { diff --git a/gapic/src/main/com/google/gapid/models/Capture.java b/gapic/src/main/com/google/gapid/models/Capture.java index f35eb14d08..00c777f0dc 100644 --- a/gapic/src/main/com/google/gapid/models/Capture.java +++ b/gapic/src/main/com/google/gapid/models/Capture.java @@ -21,6 +21,7 @@ import static com.google.gapid.util.Paths.capture; import static com.google.gapid.views.ErrorDialog.showErrorDialog; import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -31,6 +32,7 @@ import com.google.gapid.rpc.UiErrorCallback; import com.google.gapid.rpc.UiErrorCallback.ResultOrError; import com.google.gapid.server.Client; +import com.google.gapid.server.Client.InternalServerErrorException; import com.google.gapid.server.Client.UnsupportedVersionException; import com.google.gapid.util.Events; import com.google.gapid.util.Loadable; @@ -130,6 +132,15 @@ protected ResultOrError processResult(Rpc.Result r } catch (BadCaptureException e) { throttleLogRpcError(LOG, "Failed to load trace file", e); return error(Loadable.Message.error(e)); + } catch (InternalServerErrorException e) { + analytics.reportException(e); + if (e.getMessage().contains("Cause: Failed to convert")) { + LOG.log(WARNING, "Invalid capture format load error:", e); + return error(Loadable.Message.error( + "Failed to load capture: file contains unsupported, outdated, or corrupted trace data.")); + } else { + return error(Loadable.Message.error(e)); + } } catch (RpcException e) { analytics.reportException(e); return error(Loadable.Message.error(e)); diff --git a/gapic/src/main/com/google/gapid/models/CommandStream.java b/gapic/src/main/com/google/gapid/models/CommandStream.java index ee9b24f282..e48331afc1 100644 --- a/gapic/src/main/com/google/gapid/models/CommandStream.java +++ b/gapic/src/main/com/google/gapid/models/CommandStream.java @@ -18,6 +18,7 @@ import static com.google.gapid.proto.service.memory.Memory.PoolNames.Application_VALUE; import static com.google.gapid.util.Paths.command; import static com.google.gapid.util.Paths.commandTree; +import static com.google.gapid.util.Paths.commandTreeNodeForCommand; import static com.google.gapid.util.Paths.lastCommand; import static com.google.gapid.util.Paths.observationsAfter; import static com.google.gapid.widgets.Widgets.submitIfNotDisposed; @@ -25,11 +26,11 @@ import com.google.common.base.Objects; import com.google.common.base.Preconditions; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; -import com.google.gapid.models.ApiContext.FilteringContext; -import com.google.gapid.proto.device.Device.Instance; +import com.google.gapid.proto.device.Device; import com.google.gapid.proto.service.Service; import com.google.gapid.proto.service.api.API; import com.google.gapid.proto.service.path.Path; @@ -41,9 +42,13 @@ import com.google.gapid.util.Loadable; import com.google.gapid.util.MoreFutures; import com.google.gapid.util.Paths; +import com.google.gapid.views.Formatter; +import org.eclipse.jface.viewers.TreePath; import org.eclipse.swt.widgets.Shell; +import java.util.Iterator; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.function.Consumer; import java.util.function.Supplier; @@ -54,24 +59,23 @@ */ public class CommandStream extends DeviceDependentModel.ForPath - implements ApiContext.Listener, Capture.Listener, Devices.Listener { + implements Capture.Listener, Devices.Listener { protected static final Logger LOG = Logger.getLogger(CommandStream.class.getName()); private final Capture capture; - private final ApiContext context; private final ConstantSets constants; + private final Paths.CommandFilter filter; private CommandIndex selection; public CommandStream(Shell shell, Analytics analytics, Client client, Capture capture, - Devices devices, ApiContext context, ConstantSets constants) { + Devices devices, ConstantSets constants, Settings settings) { super(LOG, shell, analytics, client, Listener.class, devices); this.capture = capture; - this.context = context; this.constants = constants; + this.filter = Paths.CommandFilter.fromSettings(settings); capture.addListener(this); devices.addListener(this); - context.addListener(this); } @Override @@ -84,36 +88,22 @@ public void onCaptureLoadingStart(boolean maintainState) { @Override public void onCaptureLoaded(Loadable.Message error) { - if (error == null && selection != null) { - selection = selection.withCapture(capture.getData().path); - if (isLoaded()) { - resolve(selection.getCommand(), node -> selectCommands(selection.withNode(node), true)); + if (error == null) { + if (selection != null) { + selection = selection.withCapture(capture.getData().path); } + load(Paths.commandTree(capture.getData().path, filter), false); } } @Override - public void onReplayDeviceChanged(Instance dev) { + public void onReplayDeviceChanged(Device.Instance dev) { if (selection != null && selection.getNode() != null) { // Clear the node, so the selection will be re-resolved once the context has updated. selection = selection.withNode(null); } } - @Override - public void onContextsLoaded() { - onContextSelected(context.getSelectedContext()); - } - - @Override - public void onContextSelected(FilteringContext ctx) { - if (selection != null && selection.getNode() != null) { - // Clear the node, so the selection will be re-resolved once the context has updated. - selection = selection.withNode(null); - } - load(commandTree(capture.getData().path, ctx), false); - } - @Override protected ListenableFuture doLoad(Path.Any path, Path.Device device) { return MoreFutures.transformAsync(client.get(path, device), @@ -156,6 +146,8 @@ protected void onUiThread(Node result) { callback.run(); } }); + } else { + callback.run(); } } @@ -192,6 +184,20 @@ protected void fireLoadedEvent() { } } + public Paths.CommandFilter getFilter() { + return filter.copy(); + } + + public void setFilter(Paths.CommandFilter newFilter) { + if (filter.update(newFilter)) { + if (selection != null) { + // Clear the node, so the selection will be re-resolved once the tree has updated. + selection = selection.withNode(null); + } + load(Paths.commandTree(capture.getData().path, filter), false); + } + } + public CommandIndex getSelectedCommands() { return (selection != null && selection.getNode() != null) ? selection : null; } @@ -205,7 +211,11 @@ public void selectCommands(CommandIndex index, boolean force) { } RootNode root = (RootNode)getData(); - if (index.getNode() == null) { + if (root.getChildCount() == 0) { + // If the tree is empty, ignore any selection. + selection = null; + listeners.fire().onCommandsSelected(selection); + } else if (index.getNode() == null) { resolve(index.getCommand(), node -> selectCommands(index.withNode(node), force)); } else if (!index.getNode().getTree().equals(root.tree)) { // TODO @@ -218,7 +228,7 @@ public void selectCommands(CommandIndex index, boolean force) { private void resolve(Path.Command command, Consumer cb) { RootNode root = (RootNode)getData(); - Rpc.listen(client.get(commandTree(root.tree, command), root.device), + Rpc.listen(client.get(commandTreeNodeForCommand(root.tree, command, false), root.device), new UiCallback(shell, LOG) { @Override protected Path.CommandTreeNode onRpcThread(Rpc.Result result) @@ -242,25 +252,72 @@ public ListenableFuture getMemory(Path.Device device, CommandInd }); } + public ListenableFuture getTreePath(Path.CommandTreeNode nodePath) { + CommandStream.Node root = this.getData(); + ListenableFuture result = getTreePath(root, Lists.newArrayList(root), + nodePath.getIndicesList().iterator()); + return result; + } + + public ListenableFuture getTreePath( + CommandStream.Node node, List path, Iterator indices) { + ListenableFuture load = this.load(node); + if (!indices.hasNext()) { + TreePath result = new TreePath(path.toArray()); + // Ensure the last node in the path is loaded. + return (load == null) ? Futures.immediateFuture(result) : + MoreFutures.transform(load, ignored -> result); + } + return (load == null) ? getTreePathForLoadedNode(node, path, indices) : + MoreFutures.transformAsync(load, loaded -> getTreePathForLoadedNode(loaded, path, indices)); + } + + private ListenableFuture getTreePathForLoadedNode( + CommandStream.Node node, List path, Iterator indices) { + int index = indices.next().intValue(); + + CommandStream.Node child = node.getChild(index); + path.add(child); + return getTreePath(child, path, indices); + } + + public ListenableFuture getGroupingNodePath(Path.Command command) { + RootNode root = (RootNode)getData(); + return MoreFutures.transform( + client.get(commandTreeNodeForCommand(root.tree, command, true), root.device), + v -> v.getPath().getCommandTreeNode()); + } + + public ListenableFuture findNode(Path.CommandTreeNode nodePath) { + return MoreFutures.transform(getTreePath(nodePath), $ -> { // Load the nodes along the path. + CommandStream.Node node = this.getData(); // root. + for (long index : nodePath.getIndicesList()) { + if (index >= node.getChildCount()) { + return null; + } + node = node.getChild((int)index); + } + return node; + }); + } + /** * An index into the command stream, representing a specific "point in time" in the trace. */ public static class CommandIndex implements Comparable { private final Path.Command command; private final Path.CommandTreeNode node; - private final boolean group; - private CommandIndex(Path.Command command, Path.CommandTreeNode node, boolean group) { + private CommandIndex(Path.Command command, Path.CommandTreeNode node) { this.command = command; this.node = node; - this.group = group; } /** * Create an index pointing to the given command and node. */ public static CommandIndex forNode(Path.Command command, Path.CommandTreeNode node) { - return new CommandIndex(command, node, false); + return new CommandIndex(command, node); } /** @@ -268,23 +325,15 @@ public static CommandIndex forNode(Path.Command command, Path.CommandTreeNode no * The tree nodes is then resolved when it is needed. */ public static CommandIndex forCommand(Path.Command command) { - return new CommandIndex(command, null, false); - } - - /** - * Same as {@link #forCommand}, except that group selection is to be preferred when - * resolving to a tree node. - */ - public static CommandIndex forGroup(Path.Command command) { - return new CommandIndex(command, null, true); + return new CommandIndex(command, null); } public CommandIndex withNode(Path.CommandTreeNode newNode) { - return new CommandIndex(command, newNode, group); + return new CommandIndex(command, newNode); } public CommandIndex withCapture(Path.Capture capture) { - return new CommandIndex(command.toBuilder().setCapture(capture).build(), null, group); + return new CommandIndex(command.toBuilder().setCapture(capture).build(), null); } public Path.Command getCommand() { @@ -295,10 +344,6 @@ public Path.CommandTreeNode getNode() { return node; } - public boolean isGroup() { - return group; - } - @Override public String toString() { return command.getIndicesList().toString(); @@ -350,6 +395,10 @@ public Node getParent() { return parent; } + public int getChildIndex() { + return index; + } + public int getChildCount() { return (data == null) ? 0 : (int)data.getNumChildren(); } @@ -389,11 +438,42 @@ public Path.CommandTreeNode.Builder getPath(Path.CommandTreeNode.Builder path) { return parent.getPath(path).addIndices(index); } + public TreePath getTreePath() { + List nodes = getTreePath(Lists.newArrayList()); + return new TreePath(nodes.toArray(Node[]::new)); + } + + private List getTreePath(List nodes) { + if (parent != null) { + parent.getTreePath(nodes); + } + nodes.add(this); + return nodes; + } + + public List getCommandStart() { + return data == null ? null : data.getCommands().getFromList(); + } + + public List getCommandEnd() { + return data == null ? null : data.getCommands().getToList(); + } + public CommandIndex getIndex() { return (data == null) ? null : CommandIndex.forNode(data.getRepresentation(), getPath(Path.CommandTreeNode.newBuilder()).build()); } + public String getIndexString() { + if (data == null) { + return ""; + } else if (data.getGroup().isEmpty() && data.hasCommands()) { + return Formatter.lastIndex(data.getCommands()); + } else { + return Formatter.firstIndex(data.getCommands()); + } + } + public ListenableFuture load(Shell shell, Supplier> loader) { if (data != null) { // Already loaded. diff --git a/gapic/src/main/com/google/gapid/models/Devices.java b/gapic/src/main/com/google/gapid/models/Devices.java index 21fe458bec..9ebbb68c59 100644 --- a/gapic/src/main/com/google/gapid/models/Devices.java +++ b/gapic/src/main/com/google/gapid/models/Devices.java @@ -15,17 +15,23 @@ */ package com.google.gapid.models; +import static com.google.common.util.concurrent.Futures.immediateFuture; import static com.google.gapid.util.Logging.throttleLogRpcError; +import static com.google.gapid.util.MoreFutures.transformAsync; +import static java.util.concurrent.TimeUnit.DAYS; import static java.util.stream.Collectors.toList; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.proto.SettingsProto; import com.google.gapid.proto.device.Device; import com.google.gapid.proto.device.Device.Instance; +import com.google.gapid.proto.device.Device.VulkanDriver; import com.google.gapid.proto.service.Service; -import com.google.gapid.proto.service.Service.Value; import com.google.gapid.proto.service.path.Path; +import com.google.gapid.proto.stringtable.Stringtable; import com.google.gapid.rpc.Rpc; import com.google.gapid.rpc.Rpc.Result; import com.google.gapid.rpc.RpcException; @@ -33,15 +39,21 @@ import com.google.gapid.rpc.UiErrorCallback; import com.google.gapid.server.Client; import com.google.gapid.util.Events; +import com.google.gapid.util.Flags; +import com.google.gapid.util.Flags.Flag; import com.google.gapid.util.Loadable; import com.google.gapid.util.MoreFutures; import com.google.gapid.util.Paths; import org.eclipse.swt.widgets.Shell; +import java.io.File; +import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.logging.Logger; +import java.util.function.Consumer; /** * Model containing information about capture and replay devices. @@ -54,19 +66,28 @@ public class Devices { private final Shell shell; protected final Analytics analytics; private final Client client; + private final DeviceValidationCache validationCache; private List replayDevices; + private List incompatibleReplayDevices; private Device.Instance selectedReplayDevice; private List devices; - public Devices(Shell shell, Analytics analytics, Client client, Capture capture) { + public static final Flag skipDeviceValidation = Flags.value("skip-device-validation", false, + "Skips the device support validation process. " + + "Device support validation verifies that the GPU events emitted are within the acceptable threshold.", true); + + public Devices(Shell shell, Analytics analytics, Client client, Capture capture, Settings settings) { this.shell = shell; this.analytics = analytics; this.client = client; + this.validationCache = new DeviceValidationCache(settings); capture.addListener(new Capture.Listener() { @Override public void onCaptureLoadingStart(boolean maintainState) { - resetReplayDevice(); + if (!maintainState) { + resetReplayDevice(); + } } @Override @@ -81,21 +102,35 @@ public void onCaptureLoaded(Loadable.Message error) { protected void resetReplayDevice() { replayDevices = null; selectedReplayDevice = null; + incompatibleReplayDevices = null; } - protected void loadReplayDevices(Path.Capture capturePath) { + public void loadReplayDevices(Path.Capture capturePath) { rpcController.start().listen(MoreFutures.transformAsync(client.getDevicesForReplay(capturePath), - devs -> Futures.allAsList(devs.stream() - .map(dev -> client.get(Paths.device(dev), dev)) - .collect(toList()))), - new UiErrorCallback, List, Void>(shell, LOG) { + devs -> { + ListenableFuture> allDevices = MoreFutures.transform( + Futures.allAsList(devs.getListList().stream() + .map(d -> client.get(Paths.device(d), d)) + .collect(toList())), + l -> l.stream().map(v -> v.getDevice()).collect(toList())); + + List compatibilities = devs.getCompatibilitiesList(); + List reasons = devs.getReasonsList(); + + return MoreFutures.transform(allDevices, instances -> { + List replayDevs = Lists.newArrayList(); + for (int i = 0; i < instances.size(); ++i) { + replayDevs.add(new ReplayDeviceInfo(instances.get(i), compatibilities.get(i), reasons.get(i))); + } + return replayDevs; + }); + }), + + new UiErrorCallback, List, Void>(shell, LOG) { @Override - protected ResultOrError, Void> onRpcThread(Result> result) { + protected ResultOrError, Void> onRpcThread(Result> result) { try { - List devs = result.get().stream() - .map(v -> v.getDevice()) - .collect(toList()); - return devs.isEmpty() ? error(null) : success(devs); + return success(result.get()); } catch (RpcException | ExecutionException e) { analytics.reportException(e); throttleLogRpcError(LOG, "LoadData error", e); @@ -104,7 +139,7 @@ protected ResultOrError, Void> onRpcThread(Result devs) { + protected void onUiThreadSuccess(List devs) { updateReplayDevices(devs); } @@ -115,9 +150,21 @@ protected void onUiThreadError(Void error) { }); } - protected void updateReplayDevices(List devs) { - replayDevices = devs; - selectedReplayDevice = (devs == null) ? null : devs.get(0); + protected void updateReplayDevices(List devs) { + if (devs == null) { + replayDevices = null; + incompatibleReplayDevices = null; + } else { + replayDevices = Lists.newArrayList(); + incompatibleReplayDevices = Lists.newArrayList(); + for (ReplayDeviceInfo d: devs) { + if (d.compatible) { + replayDevices.add(d.instance); + } else { + incompatibleReplayDevices.add(d); + } + } + } listeners.fire().onReplayDevicesLoaded(); } @@ -133,6 +180,10 @@ public Device.Instance getSelectedReplayDevice() { return selectedReplayDevice; } + public List getIncompatibleReplayDevices() { + return incompatibleReplayDevices; + } + public Path.Device getReplayDevicePath() { return (selectedReplayDevice == null) ? null : Paths.device(selectedReplayDevice.getID()); } @@ -142,6 +193,50 @@ public void selectReplayDevice(Device.Instance dev) { listeners.fire().onReplayDeviceChanged(dev); } + public void validateDevice(Device.Instance device, Consumer onValidationResultCb) { + rpcController.start().listen(client.validateDevice(Paths.device(device.getID())), + new UiErrorCallback(shell, LOG) { + @Override + protected ResultOrError + onRpcThread(Rpc.Result response) throws RpcException, ExecutionException { + try { + return success(response.get()); + } catch (RpcException e) { + // Expected to get RPC exceptions for internal errors. + return error(e); + } catch (ExecutionException e) { + throttleLogRpcError(LOG, "LoadData error", e); + return error(null); + } + } + + @Override + protected void onUiThreadSuccess(Service.DeviceValidationResult r) { + DeviceValidationResult result = new DeviceValidationResult(r); + validationCache.add(device, result); + onValidationResultCb.accept(result); + } + + @Override + protected void onUiThreadError(RpcException error) { + DeviceValidationResult result = new DeviceValidationResult(error); + validationCache.add(device, result); + onValidationResultCb.accept(result); + } + }); + } + + public DeviceValidationResult getCachedValidationStatus(Device.Instance device) { + DeviceValidationResult fromCache = validationCache.getFromCache(device); + if (fromCache != null) { + return fromCache; + } else if (skipDeviceValidation.get()) { + return DeviceValidationResult.SKIPPED; + } else { + return DeviceValidationResult.FAILED; + } + } + public void loadDevices() { rpcController.start().listen(MoreFutures.transformAsync(client.getDevices(), paths -> { List> results = Lists.newArrayList(); @@ -183,18 +278,28 @@ protected void updateDevices(List newDevices) { listeners.fire().onCaptureDevicesLoaded(); } + public ListenableFuture installApp(Device.Instance device, File file) { + return client.installApp(Paths.device(device.getID()), file.getAbsolutePath()); + } + public boolean isLoaded() { return devices != null; } + public boolean isReplayDevicesLoaded() { + return replayDevices != null; + } + public List getAllDevices() { - return (devices == null) ? null : - devices.stream().map(info -> info.device).collect(toList()); + if (devices != null) { + return devices.stream().map(info -> info.device).collect(toList()); + } + return Collections.emptyList(); } public List getCaptureDevices() { return (devices == null) ? null : - devices.stream().filter(info -> !info.config.getApisList().isEmpty()).collect(toList()); + devices.stream().filter(info -> !info.config.getTypesList().isEmpty()).collect(toList()); } public void addListener(Listener listener) { @@ -205,6 +310,30 @@ public void removeListener(Listener listener) { listeners.removeListener(listener); } + public static String getDriverVersion(Device.Instance device) { + Device.VulkanDriver vkDriver = device.getConfiguration().getDrivers().getVulkan(); + if (vkDriver.getPhysicalDevicesCount() <= 0) { + return "no physical device found"; + } + return Integer.toUnsignedString(vkDriver.getPhysicalDevices(0).getDriverVersion()); + } + + public static String getVulkanDriverVersions(Device.Instance dev) { + StringBuilder version = new StringBuilder("N/A"); + VulkanDriver vkDriver = dev.getConfiguration().getDrivers().getVulkan(); + boolean first = true; + for (int i = 0; i < vkDriver.getPhysicalDevicesCount(); i++) { + if (first) { + version.setLength(0); + first = false; + } else { + version.append(", "); + } + version.append(Integer.toUnsignedString(vkDriver.getPhysicalDevices(0).getDriverVersion())); + } + return version.toString(); + } + public static String getLabel(Device.Instance dev) { StringBuilder sb = new StringBuilder(); if (!dev.getName().isEmpty()) { @@ -227,6 +356,7 @@ private static StringBuilder appendOsLabel(StringBuilder sb, Device.OS os) { case Linux: sb.append("Linux"); break; case Windows: sb.append("Windows"); break; case OSX: sb.append("MacOS"); break; + case Fuchsia: sb.append("Fuchsia"); break; default: sb.append("Unknown OS"); break; } if (!os.getName().isEmpty()) { @@ -268,6 +398,20 @@ public default void onReplayDeviceChanged(Device.Instance dev) { /* empty */ } public default void onCaptureDevicesLoaded() { /* empty */ } } + /** + * Encapsulates information about a replay device. + */ + public static class ReplayDeviceInfo { + public final Device.Instance instance; + public final Boolean compatible; + public final Stringtable.Msg reason; + + public ReplayDeviceInfo(Instance instance, Boolean compatible, Stringtable.Msg reason) { + this.instance = instance; + this.compatible = compatible; + this.reason = reason; + } + } /** * Encapsulates information about a Device and what trace options @@ -291,8 +435,173 @@ public boolean isAndroid() { return device.getConfiguration().getOS().getKind() == Device.OSKind.Android; } - public boolean isStadia() { - return device.getConfiguration().getOS().getKind() == Device.OSKind.Stadia; + public boolean isFuchsia() { + return device.getConfiguration().getOS().getKind() == Device.OSKind.Fuchsia; + } + + /** + * Returns this device's tracing capabilities for the given type. Returns {@code null} if the + * given type is not supported by this device. + */ + public final Service.TraceTypeCapabilities getTypeCapabilities(Service.TraceType type) { + for (Service.TraceTypeCapabilities cap : config.getTypesList()) { + if (cap.getType() == type) { + return cap; + } + } + return null; + } + } + + public static class DeviceValidationResult { + public static final DeviceValidationResult PASSED = new DeviceValidationResult( + Service.DeviceValidationResult.ErrorCode.OK, "", "", false); + public static final DeviceValidationResult FAILED = new DeviceValidationResult( + Service.DeviceValidationResult.ErrorCode.FAILED_PRECONDITION, "", "", false); + public static final DeviceValidationResult SKIPPED = new DeviceValidationResult( + Service.DeviceValidationResult.ErrorCode.OK, "", "", true); + + // Corresponds to the error code from service.proto, but also includes Internal + public static enum ErrorCode { + Invalid, + Ok, + FailedPrecondition, + FailedTraceValidation, + Internal, + } + + public final ErrorCode errorCode; + public final String validationFailureMsg; + public final String tracePath; + public final boolean skipped; + + public DeviceValidationResult(Service.DeviceValidationResult r) { + this(r.getErrorCode(), + r.getValidationFailureMsg(), + r.getTracePath(), + false); + } + + public DeviceValidationResult(RpcException e) { + this(ErrorCode.Internal, e.toString(), "", false); + } + + private DeviceValidationResult(Service.DeviceValidationResult.ErrorCode errorCode, String validationFailureMsg, String tracePath, boolean skipped) { + this.errorCode = ConvertErrorCode(errorCode); + this.validationFailureMsg = validationFailureMsg; + this.tracePath = tracePath; + this.skipped = skipped; + } + + private DeviceValidationResult(ErrorCode errorCode, String validationFailureMsg, String tracePath, boolean skipped) { + this.errorCode = errorCode; + this.validationFailureMsg = validationFailureMsg; + this.tracePath = tracePath; + this.skipped = skipped; + } + + private ErrorCode ConvertErrorCode(Service.DeviceValidationResult.ErrorCode errorCode) { + switch (errorCode) { + case OK: + return ErrorCode.Ok; + case FAILED_PRECONDITION: + return ErrorCode.FailedPrecondition; + case FAILED_TRACE_VALIDATION: + return ErrorCode.FailedTraceValidation; + default: + return ErrorCode.Invalid; + } + } + + public boolean passed() { + return errorCode == ErrorCode.Ok; + } + + + public boolean passedOrSkipped() { + return passed() || skipped; + } + + @Override + public String toString() { + return errorCode.toString() + + (errorCode != ErrorCode.Ok ? " Error" : "") + + ": " + validationFailureMsg; + } + } + + private static class DeviceValidationCache { + private static final long MAX_VALIDATION_AGE = DAYS.toMillis(30); + + private final Map cache = + Maps.newHashMap(); // We only remember passed validations. + private final SettingsProto.DeviceValidation.Builder stored; + + public DeviceValidationCache(Settings settings) { + this.stored = settings.writeDeviceValidation(); + for (int i = 0; i < stored.getValidationEntriesCount(); i++) { + SettingsProto.DeviceValidation.ValidationEntry.Builder entry = + stored.getValidationEntriesBuilder(i); + if ((System.currentTimeMillis() - entry.getLastSeen()) > MAX_VALIDATION_AGE) { + stored.removeValidationEntries(i); + i--; + } else if (entry.getResult().getPassed()) { + cache.put(new Key(entry.getDevice()), entry); + } + } + } + + public DeviceValidationResult getFromCache(Device.Instance device) { + SettingsProto.DeviceValidation.ValidationEntry.Builder entry = cache.get(new Key(device)); + if (entry == null) { + return null; + } else { + entry.setLastSeen(System.currentTimeMillis()); + return DeviceValidationResult.PASSED; + } + } + + public DeviceValidationResult add(Device.Instance device, DeviceValidationResult result) { + if (result.passed()) { + Key key = new Key(device); + cache.put(key, stored.addValidationEntriesBuilder() + .setDevice(key.device) + .setLastSeen(System.currentTimeMillis()) + .setResult(SettingsProto.DeviceValidation.Result.newBuilder() + .setPassed(true))); + } + return result; + } + + private static class Key { + public final SettingsProto.DeviceValidation.Device device; + + public Key(Device.Instance device) { + this.device = SettingsProto.DeviceValidation.Device.newBuilder() + .setSerial(device.getSerial()) + .setOs(device.getConfiguration().getOS()) + .setVersion(device.getConfiguration().getDrivers().getVulkan().getVersion()) + .build(); + } + + public Key(SettingsProto.DeviceValidation.Device device) { + this.device = device; + } + + @Override + public int hashCode() { + return device.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof Key)) { + return false; + } + return device.equals(((Key)obj).device); + } } } } diff --git a/gapic/src/main/com/google/gapid/models/Follower.java b/gapic/src/main/com/google/gapid/models/Follower.java index 4d2e1ab561..812144ff67 100644 --- a/gapic/src/main/com/google/gapid/models/Follower.java +++ b/gapic/src/main/com/google/gapid/models/Follower.java @@ -231,6 +231,12 @@ public void onFollow(Path.Any path) { case STATE: listeners.fire().onStateFollowed(path); break; + case RESOURCE_DATA: + listeners.fire().onResourceFollowed(path.getResourceData()); + break; + case FRAMEBUFFER_ATTACHMENT: + listeners.fire().onFramebufferAttachmentFollowed(path.getFramebufferAttachment()); + break; default: LOG.log(WARNING, "Unknown follow path result: " + path); } @@ -259,6 +265,16 @@ public default void onStateFollowed(Path.Any path) { /* empty */ } * Event indicating that a link with the given path to a memory region was followed. */ public default void onMemoryFollowed(Path.Memory path) { /* empty */ } + + /** + * Event indicating that a link to the given resource was followed. + */ + public default void onResourceFollowed(Path.ResourceData path) { /* empty */ } + + /** + * Event indicating that a link with the given path to a framebuffer attachment was followed. + */ + public default void onFramebufferAttachmentFollowed(Path.FramebufferAttachment path) {} } public static interface Prefetcher { diff --git a/gapic/src/main/com/google/gapid/models/Geometries.java b/gapic/src/main/com/google/gapid/models/Geometries.java index 4e4add6e28..e53121abe5 100644 --- a/gapic/src/main/com/google/gapid/models/Geometries.java +++ b/gapic/src/main/com/google/gapid/models/Geometries.java @@ -38,6 +38,7 @@ import com.google.gapid.rpc.UiErrorCallback.ResultOrError; import com.google.gapid.server.Client; import com.google.gapid.server.Client.DataUnavailableException; +import com.google.gapid.server.Client.InvalidArgumentException; import com.google.gapid.util.Events; import com.google.gapid.util.Loadable; import com.google.gapid.util.MoreFutures; @@ -200,7 +201,7 @@ private static ListenableFuture fetchModel(API.Mesh mesh) { API.DrawPrimitive primitive = mesh.getDrawPrimitive(); if (positions == null || (normals == null && isPolygon(primitive))) { return Futures.immediateFailedFuture( - new DataUnavailableException(NO_MESH_ERR, new Client.Stack(() -> ""))); + new InvalidArgumentException(NO_MESH_ERR, new Client.Stack(() -> ""))); } int[] indices = mesh.getIndexBuffer().getIndicesList().stream().mapToInt(x -> x).toArray(); diff --git a/gapic/src/main/com/google/gapid/models/ImagesModel.java b/gapic/src/main/com/google/gapid/models/ImagesModel.java index be54f15f8e..cf69e8bbff 100644 --- a/gapic/src/main/com/google/gapid/models/ImagesModel.java +++ b/gapic/src/main/com/google/gapid/models/ImagesModel.java @@ -22,7 +22,6 @@ import com.google.gapid.models.CommandStream.CommandIndex; import com.google.gapid.proto.image.Image; import com.google.gapid.proto.service.Service; -import com.google.gapid.proto.service.api.API; import com.google.gapid.proto.service.path.Path; import com.google.gapid.server.Client; import com.google.gapid.util.MoreFutures; @@ -43,9 +42,12 @@ public class ImagesModel { public static final int THUMB_SIZE = 192; private static final int MIN_SIZE = DPIUtil.autoScaleUp(18); private static final int THUMB_PIXELS = DPIUtil.autoScaleUp(THUMB_SIZE); - private static final Service.UsageHints FB_HINTS = Service.UsageHints.newBuilder() + private static final Path.UsageHints FB_HINTS = Path.UsageHints.newBuilder() .setPrimary(true) .build(); + private static final Path.UsageHints PREV_HINTS = Path.UsageHints.newBuilder() + .setPreview(true) + .build(); private final Client client; private final Devices devices; @@ -64,10 +66,11 @@ public boolean isReady() { } public ListenableFuture getFramebuffer(CommandIndex command, - API.FramebufferAttachment attachment, Service.RenderSettings renderSettings) { - return FetchedImage.load(client, getReplayDevice(), client.getFramebufferAttachment( - getReplayDevice(), command.getCommand(), attachment, renderSettings, - FB_HINTS, shouldDisableReplayOptimization())); + int attachment, Path.RenderSettings renderSettings) { + Path.Any fbPath = Paths.framebufferAttachmentAfter(command, attachment, renderSettings, FB_HINTS); + + return MoreFutures.transformAsync(client.get(fbPath, getReplayDevice()), + value -> FetchedImage.load(client, getReplayDevice(), value.getFramebufferAttachment().getImageInfo())); } public ListenableFuture getResource(Path.ResourceData path) { @@ -77,19 +80,37 @@ public ListenableFuture getResource(Path.ResourceData path) { public ListenableFuture getThumbnail( Path.Command command, int size, Consumer onInfo) { return MoreFutures.transform(loadThumbnail(client, getReplayDevice(), thumbnail(command), onInfo), - image -> processImage(image, size)); + image -> scaleImage(image, size)); } public ListenableFuture getThumbnail( Path.CommandTreeNode node, int size, Consumer onInfo) { return MoreFutures.transform(loadThumbnail(client, getReplayDevice(), thumbnail(node), onInfo), - image -> processImage(image, size)); + image -> scaleImage(image, size)); } public ListenableFuture getThumbnail( Path.ResourceData resource, int size, Consumer onInfo) { return MoreFutures.transform(loadThumbnail(client, getReplayDevice(), thumbnail(resource), onInfo), - image -> processImage(image, size)); + image -> scaleImage(image, size)); + } + + public ListenableFuture getThumbnail(CommandIndex command, + int attachment, int size, Consumer onInfo) { + return MoreFutures.transform(loadThumbnail(client, getReplayDevice(), thumbnail(command, attachment), onInfo), + image -> scaleImage(image, size)); + } + + public ListenableFuture getThumbnail(Image.Info info, int size) { + return MoreFutures.transform(loadThumbnail(client, getReplayDevice(), info), + image -> scaleImage(image, size)); + } + + public ListenableFuture getAllTextureThumbnails(CommandIndex command) { + return MoreFutures.transform( + client.get(thumbnails(command, Path.ResourceType.Texture), getReplayDevice()), res -> { + return res.getMultiResourceThumbnail(); + }); } private Path.Thumbnail thumbnail(Path.Command command) { @@ -104,6 +125,14 @@ private Path.Thumbnail thumbnail(Path.ResourceData resource) { return Paths.thumbnail(resource, THUMB_PIXELS, shouldDisableReplayOptimization()); } + private Path.Thumbnail thumbnail(CommandIndex command, int attachment) { + return Paths.thumbnail(command, attachment, THUMB_PIXELS, shouldDisableReplayOptimization()); + } + + private Path.Any thumbnails(CommandIndex command, Path.ResourceType type) { + return Paths.thumbnails(command, type, THUMB_PIXELS, shouldDisableReplayOptimization()); + } + private Path.Device getReplayDevice() { return devices.getReplayDevicePath(); } @@ -112,7 +141,7 @@ private boolean shouldDisableReplayOptimization() { return settings.preferences().getDisableReplayOptimization(); } - private static ImageData processImage(ImageData image, int size) { + public static ImageData scaleImage(ImageData image, int size) { size = DPIUtil.autoScaleUp(size); if (image.width >= image.height) { if (image.width > size) { diff --git a/gapic/src/main/com/google/gapid/models/Memory.java b/gapic/src/main/com/google/gapid/models/Memory.java index 96174a6458..761d2e2952 100644 --- a/gapic/src/main/com/google/gapid/models/Memory.java +++ b/gapic/src/main/com/google/gapid/models/Memory.java @@ -23,8 +23,10 @@ import static com.google.gapid.util.Ranges.merge; import static com.google.gapid.util.Ranges.relative; +import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.common.primitives.UnsignedLongs; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -53,7 +55,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.logging.Logger; +import java.util.stream.Collectors; /** * Model responsible for loading memory pool data. This model requests segments as equally sized @@ -146,7 +150,7 @@ public boolean equals(Object obj) { return false; } Source s = (Source)obj; - return command.equals(s.command) && pool == s.pool; + return Objects.equal(command, s.command) && pool == s.pool; } @Override @@ -641,38 +645,26 @@ private List loadChildren() { /** * Utility method. Simplify trees, especially for vulkan structs. * 1. Remove redundant layers for all the trees. - * 2. Identify some trees to be the main trees. (Assume the main trees to be those with type - * TypeInfo.StructType, they usually contain key info like VkPresentInfoKHR, VkSubmitInfo...) - * 3. Combine trees together by appending some smaller trees to the main trees, if they are + * 2. Combine trees together by appending some smaller trees to the main trees, if they are * related through a pointer field. */ public static List simplifyTrees(List trees) { - List simplified = new ArrayList(); - - Map nodes = new HashMap(); + // Remove redundant layers. + List allNodes = Lists.newArrayList(); + Map> nodesMap = new HashMap>(); for (StructNode tree : trees) { - nodes.put(tree.getRootAddress(), removeExtraLayers(tree)); + StructNode simpleTree = removeExtraLayers(tree); + nodesMap.putIfAbsent(tree.getRootAddress(), Lists.newArrayList()); + nodesMap.get(tree.getRootAddress()).add(simpleTree); + allNodes.add(simpleTree); } - - // Find the main trees. - for (Iterator> it = nodes.entrySet().iterator(); it.hasNext(); ) { - Map.Entry entry = it.next(); - if (containsStructType(entry.getValue())) { - simplified.add(entry.getValue()); - it.remove(); - } - } - - // Append other trees to the main trees if possible. - for (StructNode mainTree : simplified) { - appendPointedNodes(mainTree, nodes); + // Append pointed nodes to corresponding pointer field if possible. + Set appended = Sets.newHashSet(); // Whether the node is appended to other nodes, if so, it shouldn't get displayed a second time as a basic independent tree. + Set treated = Sets.newHashSet(); // Whether the node got treated so that all of its children pointer field got appended. + for (StructNode node : allNodes) { + appendPointedNodes(node, nodesMap, appended, treated); } - - // Add the remaining unappended nodes to the returning result. - for (StructNode node : nodes.values()) { - simplified.add(node); - } - return simplified; + return allNodes.stream().filter(n -> !appended.contains(n)).collect(Collectors.toList()); } /** @@ -683,12 +675,11 @@ private static StructNode removeExtraLayers(StructNode root) { // Remove the outer layer for SLICE type. E.g. {[()]} -> [()]. if ((tyCase == TyCase.SLICE) && root.hasChildren() && root.children.size() == 1 && root.children.get(0).hasChildren()) { - root = root.children.get(0); + root = removeExtraLayers(root.children.get(0)); } - // Remove the inner layer for PSEUDONYM type. E.g. {[()]} -> {()}. - if (tyCase == TyCase.PSEUDONYM && root.hasChildren() && root.children.size() == 1 - && root.children.get(0).hasChildren()) { - root.children = root.children.get(0).children; + // Treat PSEUDONYM types as leaves, and remove any children they may have had. + if (tyCase == TyCase.PSEUDONYM) { + root.children.clear(); } for (int i = 0; i < root.children.size(); i++) { root.children.set(i, removeExtraLayers(root.children.get(i))); @@ -718,19 +709,21 @@ private static boolean containsStructType(StructNode root) { * Find all the nodes with type TypeInfo.PointerType in this tree, append the pointed tree to * these nodes if possible. */ - private static void appendPointedNodes(StructNode root, Map nodes) { - if (root == null) { + private static void appendPointedNodes(StructNode root, Map> nodesMap, + Set appended, Set treated) { + if (root == null || treated.contains(root)) { return; } if (root.getTypeCase() == TyCase.POINTER) { long pointedAddress = root.getValue().getPointer().getAddress(); - if (nodes.containsKey(pointedAddress)) { - root.getChildren().add(nodes.get(pointedAddress)); - nodes.remove(pointedAddress); + if (nodesMap.containsKey(pointedAddress)) { + root.getChildren().addAll(nodesMap.get(pointedAddress)); + appended.addAll(nodesMap.get(pointedAddress)); } } + treated.add(root); for (StructNode child : root.getChildren()) { - appendPointedNodes(child, nodes); + appendPointedNodes(child, nodesMap, appended, treated); } } } diff --git a/gapic/src/main/com/google/gapid/models/MemoryTypes.java b/gapic/src/main/com/google/gapid/models/MemoryTypes.java index acbddc4ccc..3d551873f9 100644 --- a/gapic/src/main/com/google/gapid/models/MemoryTypes.java +++ b/gapic/src/main/com/google/gapid/models/MemoryTypes.java @@ -107,12 +107,12 @@ public TypeInfo.Type getType(Path.Type path) { * containing relevant information. */ public ListenableFuture loadStructNode(StructObservation structOb) { - Path.Any memoryAsType = memoryAsType(structOb.source.command, structOb.source.pool, structOb.range); - return transformAsync(client.get(memoryAsType, structOb.device), - val -> transform(loadTypes(structOb.getRange().getType()), - $ -> new StructNode(structOb.getRange().getType().getAPI(), - getType(structOb.getRange().getType()), val.getMemoryBox(), - structOb.range.getRoot(), this))); + return transform(loadTypes(structOb.getRange().getType()), $ -> + new StructNode(structOb.getRange().getType().getAPI(), + getType(structOb.getRange().getType()), + structOb.getRange().getValue(), + structOb.range.getRoot(), + this)); } /** diff --git a/gapic/src/main/com/google/gapid/models/Models.java b/gapic/src/main/com/google/gapid/models/Models.java index 87f34ceeea..9a53c0e705 100644 --- a/gapic/src/main/com/google/gapid/models/Models.java +++ b/gapic/src/main/com/google/gapid/models/Models.java @@ -17,6 +17,7 @@ import com.google.gapid.server.Client; import com.google.gapid.util.ExceptionHandler; +import com.google.gapid.util.UpdateWatcher; import com.google.gapid.views.StatusBar; import org.eclipse.swt.widgets.Shell; @@ -28,8 +29,6 @@ public class Models { public final Capture capture; public final Devices devices; public final CommandStream commands; - public final ApiContext contexts; - public final Timeline timeline; public final Resources resources; public final ApiState state; public final Reports reports; @@ -39,21 +38,21 @@ public class Models { public final Memory memory; public final MemoryTypes types; public final Perfetto perfetto; + public final Profile profile; public final StatusBar status; // The "model" part of this "widget". + public final UpdateWatcher updateWatcher; public Models(Settings settings, Analytics analytics, Follower follower, Capture capture, - Devices devices, CommandStream commands, ApiContext contexts, Timeline timeline, - Resources resources, ApiState state, Reports reports, ImagesModel images, - ConstantSets constants, Geometries geos, Memory memory, MemoryTypes types, Perfetto perfetto, - StatusBar status) { + Devices devices, CommandStream commands, Resources resources, + ApiState state, Reports reports, ImagesModel images, ConstantSets constants, Geometries geos, + Memory memory, MemoryTypes types, Perfetto perfetto, Profile profile, StatusBar status, + UpdateWatcher updateWatcher) { this.settings = settings; this.analytics = analytics; this.follower = follower; this.capture = capture; this.devices = devices; this.commands = commands; - this.contexts = contexts; - this.timeline = timeline; this.resources = resources; this.state = state; this.reports = reports; @@ -63,31 +62,48 @@ public Models(Settings settings, Analytics analytics, Follower follower, Capture this.memory = memory; this.types = types; this.perfetto = perfetto; + this.profile = profile; this.status = status; + this.updateWatcher = updateWatcher; } public static Models create( Shell shell, Settings settings, ExceptionHandler handler, Client client, StatusBar status) { Analytics analytics = new Analytics(client, settings, handler); - Follower follower = new Follower(shell, client); Capture capture = new Capture(shell, analytics, client, settings); - Devices devices = new Devices(shell, analytics, client, capture); + Devices devices = new Devices(shell, analytics, client, capture, settings); ConstantSets constants = new ConstantSets(client, devices); - ApiContext contexts = new ApiContext(shell, analytics, client, capture, devices); - Timeline timeline = new Timeline(shell, analytics, client, capture, devices, contexts); CommandStream commands = new CommandStream( - shell, analytics, client, capture, devices, contexts, constants); - Resources resources = new Resources(shell, analytics, client, capture, devices, commands); + shell, analytics, client, capture, devices, constants, settings); + Follower follower = new Follower(shell, client); + Resources resources = new Resources( + shell, analytics, client, capture, devices, commands, follower); ApiState state = new ApiState( - shell, analytics, client, devices, follower, commands, contexts, constants); - Reports reports = new Reports(shell, analytics, client, capture, devices, contexts); + shell, analytics, client, devices, follower, commands, constants); + Reports reports = new Reports(shell, analytics, client, capture, devices); ImagesModel images = new ImagesModel(client, devices, capture, settings); Geometries geometries = new Geometries(shell, analytics, client, devices, commands); Memory memory = new Memory(shell, analytics, client, devices, commands); MemoryTypes types = new MemoryTypes(client, devices, constants); Perfetto perfetto = new Perfetto(shell, analytics, client, capture, status); - return new Models(settings, analytics, follower, capture, devices, commands, contexts, timeline, - resources, state, reports, images, constants, geometries, memory, types, perfetto, status); + Profile profile = new Profile(shell, analytics, client, capture, devices, commands, settings); + UpdateWatcher updateWatcher = new UpdateWatcher(settings, client, status); + return new Models(settings, analytics, follower, capture, devices, commands, resources, state, + reports, images, constants, geometries, memory, types, perfetto, profile, status, + updateWatcher); + } + + public void reset() { + capture.reset(); + devices.resetReplayDevice(); + commands.reset(); + resources.reset(); + state.reset(); + reports.reset(); + geos.reset(); + memory.reset(); + perfetto.reset(); + profile.reset(); } public void dispose() { diff --git a/gapic/src/main/com/google/gapid/models/Perfetto.java b/gapic/src/main/com/google/gapid/models/Perfetto.java index 34acfe5402..f1af2f1ab1 100644 --- a/gapic/src/main/com/google/gapid/models/Perfetto.java +++ b/gapic/src/main/com/google/gapid/models/Perfetto.java @@ -31,8 +31,10 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.models.AsyncInfo; import com.google.gapid.perfetto.models.CounterInfo; import com.google.gapid.perfetto.models.CpuInfo; +import com.google.gapid.perfetto.models.FrameInfo; import com.google.gapid.perfetto.models.GpuInfo; import com.google.gapid.perfetto.models.ProcessInfo; import com.google.gapid.perfetto.models.QueryEngine; @@ -92,9 +94,10 @@ protected ListenableFuture doLoad(Path.Capture source) { transformAsync(withStatus("Examining the trace...", examineTrace(data)), $1 -> transformAsync(withStatus("Querying threads...", queryThreads(data)), $2 -> transformAsync(withStatus("Querying GPU info...", queryGpu(data)), $3 -> - transformAsync(withStatus("Querying counters...", queryCounters(data)), $4 -> - transform(withStatus("Enumerating tracks...", enumerateTracks(data)), $5 -> - data.build()))))); + transformAsync(withStatus("Querying Frame info...", queryFrame(data)), $4 -> + transformAsync(withStatus("Querying counters...", queryCounters(data)), $5 -> + transform(withStatus("Enumerating tracks...", enumerateTracks(data)), $6 -> + data.build())))))); } private static ListenableFuture examineTrace(Data.Builder data) { @@ -105,13 +108,19 @@ private static ListenableFuture examineTrace(Data.Builder data) { } private static ListenableFuture queryThreads(Data.Builder data) { - return ThreadInfo.listThreads(data); + return transformAsync(ThreadInfo.listThreads(data), + $ -> AsyncInfo.listAsync(data)); + } private static ListenableFuture queryGpu(Data.Builder data) { return GpuInfo.listGpus(data); } + private static ListenableFuture queryFrame(Data.Builder data) { + return FrameInfo.listFrames(data); + } + private static ListenableFuture queryCounters(Data.Builder data) { return CounterInfo.listCounters(data); } @@ -175,21 +184,29 @@ public static class Data { public final CpuInfo cpu; public final ImmutableMap processes; public final ImmutableMap threads; + public final ImmutableListMultimap asyncs; public final GpuInfo gpu; + public final FrameInfo frame; public final ImmutableMap counters; + public final ImmutableListMultimap gpuCounterGroups; public final VSync vsync; public final TrackConfig tracks; public Data(QueryEngine queries, TimeSpan traceTime, CpuInfo cpu, ImmutableMap processes, ImmutableMap threads, - GpuInfo gpu, ImmutableMap counters, VSync vsync, TrackConfig tracks) { + ImmutableListMultimap asyncs, GpuInfo gpu, FrameInfo frame, + ImmutableMap counters, + ImmutableListMultimap gpuCounterGroups, VSync vsync, TrackConfig tracks) { this.qe = queries; this.traceTime = traceTime; this.cpu = cpu; this.processes = processes; this.threads = threads; + this.asyncs = asyncs; this.gpu = gpu; + this.frame = frame; this.counters = counters; + this.gpuCounterGroups = gpuCounterGroups; this.vsync = vsync; this.tracks = tracks; } @@ -200,8 +217,11 @@ public static class Builder { private CpuInfo cpu = CpuInfo.NONE; private ImmutableMap processes; private ImmutableMap threads; + private ImmutableListMultimap asyncs; private GpuInfo gpu = GpuInfo.NONE; + private FrameInfo frame = FrameInfo.NONE; private ImmutableMap counters; + private ImmutableListMultimap gpuCounterGroups; private Map> countersByName; private VSync vsync = VSync.EMPTY; public final TrackConfig.Builder tracks = new TrackConfig.Builder(); @@ -246,6 +266,15 @@ public Builder setThreads(ImmutableMap threads) { return this; } + public ImmutableListMultimap getAsyncs() { + return asyncs; + } + + public Builder setAsyncs(ImmutableListMultimap asyncs) { + this.asyncs = asyncs; + return this; + } + public GpuInfo getGpu() { return gpu; } @@ -255,10 +284,23 @@ public Builder setGpu(GpuInfo gpu) { return this; } + public FrameInfo getFrame() { + return frame; + } + + public Builder setFrame(FrameInfo frame) { + this.frame = frame; + return this; + } + public ImmutableMap getCounters() { return counters; } + public ImmutableListMultimap getGpuCounterGroups() { + return gpuCounterGroups; + } + public ImmutableListMultimap getCounters(CounterInfo.Type type) { ImmutableListMultimap r = countersByName.get(type); return (r == null) ? ImmutableListMultimap.of() : r; @@ -271,6 +313,11 @@ public Builder setCounters(ImmutableMap counters) { return this; } + public Builder setCounterGroups(ImmutableListMultimap groups) { + this.gpuCounterGroups = groups; + return this; + } + public VSync getVSync() { return vsync; } @@ -281,8 +328,8 @@ public Builder setVSync(VSync vsync) { } public Data build() { - return new Data( - qe, traceTime, cpu, processes, threads, gpu, counters, vsync, tracks.build()); + return new Data(qe, traceTime, cpu, processes, threads, asyncs, gpu, frame, counters, + gpuCounterGroups, vsync, tracks.build()); } } } diff --git a/gapic/src/main/com/google/gapid/models/Profile.java b/gapic/src/main/com/google/gapid/models/Profile.java new file mode 100644 index 0000000000..07961176c9 --- /dev/null +++ b/gapic/src/main/com/google/gapid/models/Profile.java @@ -0,0 +1,589 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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. + */ +package com.google.gapid.models; + +import static com.google.common.base.Functions.identity; +import static com.google.gapid.rpc.UiErrorCallback.error; +import static com.google.gapid.rpc.UiErrorCallback.success; +import static com.google.gapid.util.Logging.throttleLogRpcError; +import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.Paths.lastCommand; +import static java.util.logging.Level.WARNING; +import static java.util.stream.Collectors.toMap; + +import com.google.common.base.Objects; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.CommandStream.CommandIndex; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.Unit; +import com.google.gapid.perfetto.models.CounterInfo; +import com.google.gapid.proto.service.Service; +import com.google.gapid.proto.service.Service.ProfilingData.GpuCounters.Perf; +import com.google.gapid.proto.service.path.Path; +import com.google.gapid.rpc.Rpc; +import com.google.gapid.rpc.RpcException; +import com.google.gapid.rpc.UiCallback; +import com.google.gapid.rpc.UiErrorCallback.ResultOrError; +import com.google.gapid.server.Client; +import com.google.gapid.util.Events; +import com.google.gapid.util.Loadable; +import com.google.gapid.util.MoreFutures; +import com.google.gapid.util.Paths; +import com.google.gapid.util.ProtoDebugTextFormat; +import com.google.gapid.util.Ranges; + +import org.eclipse.swt.widgets.Shell; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +public class Profile + extends CaptureDependentModel { + private static final Logger LOG = Logger.getLogger(Profile.class.getName()); + private static final int FRAME_LOOP_COUNT = 10; + + private static final int SUBMIT_LEVEL = 0; + private static final int SUBMIT_INFO_LEVEL = 1; + private static final int COMMAND_BUFFER_LEVEL = 2; + private static final int COMMAND_LEVEL = 3; + + private final Capture capture; + private final CommandStream commands; + private int selectedGroupId; + private final Settings settings; + + public Profile( + Shell shell, Analytics analytics, Client client, Capture capture, Devices devices, CommandStream commands, Settings settings) { + super(LOG, shell, analytics, client, Listener.class, capture, devices); + this.capture = capture; + this.commands = commands; + this.selectedGroupId = -1; + this.settings = settings; + } + + @Override + protected Source getSource(Capture.Data data) { + return new Source(data.path, new ProfileExperiments()); + } + + @Override + protected boolean shouldLoad(Capture.Data data) { + return data.isGraphics(); + } + + @Override + protected ListenableFuture doLoad(Source source, Path.Device device) { + int loopCount = this.settings.preferences().getUseFrameLooping() ? FRAME_LOOP_COUNT : 0; + return transform(client.profile(capture.getData().path, device, source.experiments, loopCount), r -> new Data(device, r)); + } + + @Override + protected ResultOrError processResult(Rpc.Result result) { + try { + return success(result.get()); + } catch (RpcException e) { + LOG.log(WARNING, "Failed to load the GPU profile", e); + return error(Loadable.Message.error(e)); + } catch (ExecutionException e) { + if (!shell.isDisposed()) { + throttleLogRpcError(LOG, "Failed to load the GPU profile", e); + } + return error(Loadable.Message.error("Failed to load the GPU profile")); + } + } + + @Override + protected void updateError(Loadable.Message error) { + listeners.fire().onProfileLoaded(error); + } + + @Override + protected void fireLoadStartEvent() { + listeners.fire().onProfileLoadingStart(); + } + + @Override + protected void fireLoadedEvent() { + listeners.fire().onProfileLoaded(null); + } + + public void selectGroup(Service.ProfilingData.Group group) { + if (group.getId() != selectedGroupId) { + selectedGroupId = group.getId(); + listeners.fire().onGroupSelected(group); + } + } + + public void linkCommandToGpuGroup(List commandIndex) { + if (getData() == null || commandIndex == null) { + return; + } + for (Service.ProfilingData.Group group : getData().getGroups()) { + if (group.getLink().getToList().toString().equals(commandIndex.toString())) { + selectGroup(group); + } + } + } + + public void linkGpuGroupToCommand(Service.ProfilingData.Group group) { + // Use a real CommandStream.Node's CommandIndex to trigger the command selection, rather + // than using a CommandIndex stitched together on the spot. In this way the selection + // behavior aligns to what happens when selection is from the UI side, where the resource + // tabs' loading result is based on a "representation" command in the grouping node. + ListenableFuture node = MoreFutures.transformAsync( + commands.getGroupingNodePath(lastCommand(group.getLink())), + commands::findNode); + Rpc.listen(node, new UiCallback(shell, LOG) { + @Override + protected CommandStream.Node onRpcThread(Rpc.Result result) + throws RpcException, ExecutionException { + return result.get(); + } + + @Override + protected void onUiThread(CommandStream.Node result) { + selectNode(group, result); + } + }); + } + + protected void selectNode(Service.ProfilingData.Group group, CommandStream.Node node) { + if (node == null) { + // A fallback. + LOG.log(WARNING, "Profile: failed to find the CommandStream.Node for command index: %s", + ProtoDebugTextFormat.shortDebugString(group.getLink())); + commands.selectCommands(CommandIndex.forCommand(lastCommand(group.getLink())), false); + } else { + commands.selectCommands(node.getIndex(), false); + } + } + + public ProfileExperiments getExperiments() { + DeviceDependentModel.Source src = getSource(); + return (src == null || src.source == null) ? null : src.source.experiments; + } + + public void updateExperiments(ProfileExperiments experiments) { + load(Source.withExperiments(getSource(), experiments), false); + } + + public static class Source { + public final Path.Capture capture; + public final ProfileExperiments experiments; + + public Source(Path.Capture capture, ProfileExperiments experiments) { + this.capture = capture; + this.experiments = experiments; + } + + public static DeviceDependentModel.Source withExperiments( + DeviceDependentModel.Source src, ProfileExperiments newExperiments) { + Source me = (src == null) ? null : src.source; + return new DeviceDependentModel.Source((src == null) ? null : src.device, + new Source((me == null) ? null : me.capture, newExperiments)); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } else if (!(obj instanceof Source)) { + return false; + } + Source s = (Source)obj; + return Objects.equal(capture, s.capture) && Objects.equal(experiments, s.experiments); + } + + @Override + public int hashCode() { + return (capture == null) ? 0 : capture.hashCode(); + } + } + + public static class Data extends DeviceDependentModel.Data { + public final Service.ProfilingData profile; + private final PerfNode rootNode; + + public Data(Path.Device device, Service.ProfilingData profile) { + super(device); + this.profile = profile; + this.rootNode = createPerfNodes(profile); + } + + private static PerfNode createPerfNodes(Service.ProfilingData profile) { + Map entries = + profile.getGpuCounters().getEntriesList().stream() + .collect(toMap(Service.ProfilingData.GpuCounters.Entry::getGroupId, identity())); + Map nodes = Maps.newHashMap(); // groupId -> node. + nodes.put(0, new PerfNode(null, null)); + // Groups in the proto are sorted and parents come before children. + for (Service.ProfilingData.Group group : profile.getGroupsList()) { + PerfNode node = new PerfNode(entries.get(group.getId()), group); + nodes.put(group.getId(), node); + nodes.get(group.getParentId()).addChild(node); + } + return nodes.get(0); + } + + public List getGroups() { + return profile.getGroupsList(); + } + + public boolean hasSlices() { + return profile.getSlices().getSlicesCount() > 0 && + profile.getSlices().getTracksCount() > 0; + } + + public Service.ProfilingData.GpuSlices getSlices() { + return profile.getSlices(); + } + + public List getCounters() { + return profile.getCountersList(); + } + + public Service.ProfilingData.GpuCounters getGpuPerformance() { + return profile.getGpuCounters(); + } + + public List getCounterGroups() { + return profile.getCounterGroupsList(); + } + + // TODO: this function makes some assumptions about command/sub command IDs. For more details + // see gapis/trace/android/profile/groups.go. + public PerfNode getPerfNode(Service.CommandTreeNode treeNode) { + Path.Commands commands = treeNode.getCommands(); + if (commands.getFromCount() == SUBMIT_INFO_LEVEL + 1) { + // We don't have perf data for submit infos, bail out early. + return null; + } + PerfNode submit = rootNode.findNode(commands, SUBMIT_LEVEL); + if (submit == null || commands.getFromCount() == SUBMIT_LEVEL + 1) { + return submit; + } + PerfNode cmdBuf = submit.findNode(commands, COMMAND_BUFFER_LEVEL); + if (cmdBuf == null || commands.getFromCount() == COMMAND_BUFFER_LEVEL + 1) { + return cmdBuf; + } + PerfNode renderpass = cmdBuf.findNodeContaining(commands); + if (renderpass == null || Ranges.compare(renderpass.getGroup().getLink(), commands) == 0) { + return renderpass; + } + PerfNode drawCall = renderpass.findNodeContaining(commands); + if (drawCall != null) { + return drawCall; + } + + // A node with possibly multiple draw call child nodes was selected in the tree. This happens, + // for example, if debug markers are used. If a single draw call is found, just use it, + // otherwise return a synthetic node wrapping all draw calls in the range. + List drawCalls = renderpass.findAllNodesContaining(commands); + if (drawCalls.isEmpty()) { + return null; + } else if (drawCalls.size() == 1) { + return drawCalls.get(0); + } else { + return new PerfNode(drawCalls); + } + } + + public TimeSpan getSlicesTimeSpan() { + if (!hasSlices()) { + return TimeSpan.ZERO; + } + + long start = Long.MAX_VALUE, end = 0; + for (Service.ProfilingData.GpuSlices.Slice slice : profile.getSlices().getSlicesList()) { + start = Math.min(slice.getTs(), start); + end = Math.max(slice.getTs() + slice.getDur(), end); + } + return new TimeSpan(start, end); + } + } + + public static class PerfNode { + private final Service.ProfilingData.GpuCounters.Entry entry; + private final Service.ProfilingData.Group group; + protected final List children; + private final Map computedPerfValues = Maps.newHashMap(); + + public PerfNode( + Service.ProfilingData.GpuCounters.Entry entry, Service.ProfilingData.Group group) { + this.entry = entry; + this.group = group; + this.children = Lists.newArrayList(); + } + + public PerfNode(List children) { + this.entry = Service.ProfilingData.GpuCounters.Entry.getDefaultInstance(); + this.group = null; + this.children = children; + } + + public Service.ProfilingData.Group getGroup() { + return group; + } + + public Value getPerf(Service.ProfilingData.GpuCounters.Metric metric) { + Service.ProfilingData.GpuCounters.Perf perf = + entry.getMetricToValueOrDefault(metric.getId(), null); + if (perf != null) { + return new PerfValue(perf, metric); + } + + // A metric was requested that requires us to aggregate its value from our children. + // This is currently only necessary for static analysis counters. + Value value = computedPerfValues.get(metric.getId()); + if (value == null) { + // Compute the value and remember it. + switch (metric.getType()) { + case StaticAnalysisRanged: + ComputedRangeValue range = null; + for (PerfNode child : children) { + range = ComputedRangeValue.aggregate(range, child.getPerf(metric)); + } + value = range; + break; + case StaticAnalysisSummed: + ComputedSummedValue sum = null; + for (PerfNode child : children) { + sum = ComputedSummedValue.aggregate(sum, child.getPerf(metric)); + } + value = sum; + break; + default: + return Value.NULL; + } + computedPerfValues.put(metric.getId(), value); + } + return (value == null) ? Value.NULL : value; + } + + protected void addChild(PerfNode node) { + children.add(node); + } + + // Finds this node's child that matches the given command path up to the given level. + protected PerfNode findNode(Path.Commands commands, int level) { + int idx = Collections.binarySearch(children, null, (node, $) -> { + Path.Commands current = node.group.getLink(); + int result = 0; + for (int i = 0; result == 0 && i <= level; i++) { + result = Long.compare(current.getFrom(i), commands.getFrom(i)); + if (result == 0) { + result = Long.compare(current.getTo(i), commands.getTo(i)); + } + } + return result; + }); + return idx < 0 ? null : children.get(idx); + } + + // Finds this node's child which fully contains the given command path. + protected PerfNode findNodeContaining(Path.Commands commands) { + int idx = Collections.binarySearch(children, null, (node, $) -> { + Path.Commands current = node.group.getLink(); + int result = 0; + for (int i = 0; result == 0 && i <= COMMAND_BUFFER_LEVEL; i++) { + result = Long.compare(current.getFrom(i), commands.getFrom(i)); + if (result == 0) { + result = Long.compare(current.getTo(i), commands.getTo(i)); + } + } + if (result == 0) { + result = Long.compare(current.getFrom(COMMAND_LEVEL), commands.getTo(COMMAND_LEVEL)); + if (result <= 0) { + result = Long.compare(current.getTo(COMMAND_LEVEL), commands.getTo(COMMAND_LEVEL)); + if (result >= 0) { + result = 0; + } + } + } + return result; + }); + return idx < 0 ? null : children.get(idx); + } + + // Finds all of this node's children, whose range intersects with the given command path. + protected List findAllNodesContaining(Path.Commands commands) { + // Find the first child whose range intersects with commands. + int start = Collections.binarySearch(children, null, (node, $) -> + Paths.compareCommands(node.group.getLink().getToList(), commands.getFromList(), false)); + + // Collect all children whose range intersects with commands. + List result = Lists.newArrayList(); + for (int idx = (start < 0) ? -start - 1 : start; idx < children.size(); idx++) { + PerfNode child = children.get(idx); + if (Paths.compareCommands( + child.group.getLink().getToList(), commands.getToList(), false) <= 0) { + result.add(child); + } else { + break; + } + } + return result; + } + + public static interface Value { + public static final Value NULL = new Value() { + @Override + public boolean hasInterval() { + return false; + } + + @Override + public String format(boolean estimate) { + return ""; + } + }; + + public boolean hasInterval(); + public String format(boolean estimate); + } + + private static class PerfValue implements Value { + public final Service.ProfilingData.GpuCounters.Perf perf; + private final Service.ProfilingData.GpuCounters.Metric metric; + + public PerfValue(Perf perf, Service.ProfilingData.GpuCounters.Metric metric) { + this.perf = perf; + this.metric = metric; + } + + @Override + public boolean hasInterval() { + return perf.getMin() != perf.getMax(); + } + + @Override + public String format(boolean estimate) { + if (metric.getType() != Service.ProfilingData.GpuCounters.Metric.Type.Hardware) { + return String.format("%,d", (long)perf.getEstimate()); + } + Unit unit = CounterInfo.unitFromString(metric.getUnit()); + if (estimate || perf.getMin() == perf.getMax()) { + return unit.format(perf.getEstimate()); + } else { + String minStr = perf.getMin() < 0 ? "?" : unit.format(perf.getMin()); + String maxStr = perf.getMax() < 0 ? "?" : unit.format(perf.getMax()); + return "Min: " + minStr + "\nMax: " + maxStr; + } + } + } + + private static class ComputedRangeValue implements Value { + public final double min; + public final double max; + + public ComputedRangeValue(double min, double max) { + this.min = min; + this.max = max; + } + + @Override + public boolean hasInterval() { + return false; + } + + @Override + public String format(boolean estimate) { + if (min == max) { + return String.format("%,d", (long)min); + } + + return String.format("%,d - %,d", (long)min, (long)max); + } + + public static ComputedRangeValue aggregate(ComputedRangeValue a, Value b) { + if (a == null) { + if (b instanceof PerfValue) { + double v = ((PerfValue)b).perf.getEstimate(); + return new ComputedRangeValue(v, v); + } else if (b instanceof ComputedRangeValue) { + return (ComputedRangeValue)b; + } + } else if (b instanceof PerfValue) { + double v = ((PerfValue)b).perf.getEstimate(); + return new ComputedRangeValue(Math.min(a.min, v), Math.max(a.max, v)); + } else if (b instanceof ComputedRangeValue) { + ComputedRangeValue v = (ComputedRangeValue)b; + return new ComputedRangeValue(Math.min(a.min, v.min), Math.max(a.max, v.max)); + } + return a; + } + } + + private static class ComputedSummedValue implements Value { + public final double value; + + public ComputedSummedValue(double value) { + this.value = value; + } + + @Override + public boolean hasInterval() { + return false; + } + + @Override + public String format(boolean estimate) { + return String.format("%,d", (long)value); + } + + public static ComputedSummedValue aggregate(ComputedSummedValue a, Value b) { + if (a == null) { + if (b instanceof PerfValue) { + return new ComputedSummedValue(((PerfValue)b).perf.getEstimate()); + } else if (b instanceof ComputedSummedValue) { + return (ComputedSummedValue)b; + } + } else if (b instanceof PerfValue) { + return new ComputedSummedValue(a.value + ((PerfValue)b).perf.getEstimate()); + } else if (b instanceof ComputedSummedValue) { + return new ComputedSummedValue(a.value + ((ComputedSummedValue)b).value); + } + return a; + } + } + } + + public static interface Listener extends Events.Listener { + /** + * Event indicating that profiling data is being loaded. + */ + public default void onProfileLoadingStart() { /* empty */ } + + /** + * Event indicating that the profiling data has finished loading. + * + * @param error the loading error or {@code null} if loading was successful. + */ + public default void onProfileLoaded(Loadable.Message error) { /* empty */ } + + /** + * Event indicating that the currently selected group has changed. + * + * @param group the selected group. + */ + public default void onGroupSelected(Service.ProfilingData.Group group) { /* empty */ } + } +} diff --git a/gapic/src/main/com/google/gapid/models/ProfileExperiments.java b/gapic/src/main/com/google/gapid/models/ProfileExperiments.java new file mode 100644 index 0000000000..266b6ffee5 --- /dev/null +++ b/gapic/src/main/com/google/gapid/models/ProfileExperiments.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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. + */ +package com.google.gapid.models; + +import com.google.common.collect.ImmutableList; +import com.google.gapid.proto.service.Service; +import com.google.gapid.proto.service.path.Path; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public final class ProfileExperiments { + public final boolean disableAnisotropicFiltering; + public final ImmutableList disabledCommands; + + public ProfileExperiments() { + this(false, Collections.emptyList()); + } + + public ProfileExperiments(boolean disableAnisotropicFiltering, List disabledCommands) { + this.disableAnisotropicFiltering = disableAnisotropicFiltering; + this.disabledCommands = ImmutableList.copyOf(disabledCommands); + } + + public final Service.ProfileExperiments toProto() { + return Service.ProfileExperiments.newBuilder() + .setDisableAnisotropicFiltering(disableAnisotropicFiltering) + .addAllDisabledCommands(disabledCommands) + .build(); + } +} diff --git a/gapic/src/main/com/google/gapid/models/Reports.java b/gapic/src/main/com/google/gapid/models/Reports.java index 7ee219b419..d5590f448a 100644 --- a/gapic/src/main/com/google/gapid/models/Reports.java +++ b/gapic/src/main/com/google/gapid/models/Reports.java @@ -16,7 +16,6 @@ package com.google.gapid.models; import com.google.common.util.concurrent.ListenableFuture; -import com.google.gapid.models.ApiContext.FilteringContext; import com.google.gapid.proto.service.Service; import com.google.gapid.proto.service.Service.Report; import com.google.gapid.proto.service.path.Path; @@ -36,30 +35,28 @@ public class Reports extends DeviceDependentModel.ForPath { +public class Resources extends CaptureDependentModel.ForValue + implements Follower.Listener { private static final Logger LOG = Logger.getLogger(Resources.class.getName()); protected final Capture capture; private final CommandStream commands; + protected Service.Resource selectedShader = null; + protected Service.Resource selectedTexture = null; + public Resources(Shell shell, Analytics analytics, Client client, Capture capture, - Devices devices, CommandStream commands) { + Devices devices, CommandStream commands, Follower follower) { super(LOG, shell, analytics, client, Listener.class, capture, devices); this.capture = capture; this.commands = commands; + + follower.addListener(this); } @Override @@ -79,7 +90,8 @@ protected Data unbox(Service.Value value, Path.Device device) { @Override protected void fireLoadStartEvent() { - // Do nothing. + selectShader(null); + selectTexture(null); } @Override @@ -87,17 +99,44 @@ protected void fireLoadedEvent() { listeners.fire().onResourcesLoaded(); } + @Override + public void onResourceFollowed(Path.ResourceData path) { + Resources.Resource r = getResource(path); + + if (r != null) { + switch (r.resource.getType()) { + case Shader: + selectShader(r.resource); + break; + case Texture: + selectTexture(r.resource); + break; + default: + LOG.log(WARNING, "Unknown follow path result: " + path); + } + } else { + LOG.log(WARNING, "Path resolved to null resource: " + path); + } + } + public List getResources() { return getData().resources.getTypesList(); } - public ResourceList getResources(API.ResourceType type) { + public ResourceList getResources(Path.ResourceType type) { if (!isLoaded() || commands.getSelectedCommands() == null) { return new ResourceList(type, emptyList(), false); } return getData().getResources(commands.getSelectedCommands().getCommand(), type); } + public Resource getResource(Path.ResourceData path) { + if (!isLoaded() || commands.getSelectedCommands() == null) { + return null; + } + return getData().getResource(path); + } + public Path.ResourceData getResourcePath(Service.Resource resource) { CommandIndex after = commands.getSelectedCommands(); return (after == null) ? null : Path.ResourceData.newBuilder() @@ -118,7 +157,30 @@ public ListenableFuture loadResource(Service.Resource resource Service.Value::getResourceData); } - public ListenableFuture loadBoundPipelines() { + public ListenableFuture loadResourceExtras(Service.Resource resource) { + CommandIndex after = commands.getSelectedCommands(); + if (after == null) { + return Futures.immediateFailedFuture(new RuntimeException("No command selected")); + } + + // TODO: don't get the device via getData + return MoreFutures.transform( + client.get(resourceExtrasAfter(after, resource.getID()), getData().device), + Service.Value::getResourceExtras); + } + + public ListenableFuture loadResources(Path.ResourceType type) { + CommandIndex after = commands.getSelectedCommands(); + if (after == null) { + return Futures.immediateFailedFuture(new RuntimeException("No command selected")); + } + + return MoreFutures.transform( + client.get(resourcesAfter(after, type), getData().device), + Service.Value::getMultiResourceData); + } + + public ListenableFuture loadBoundPipelines() { CommandIndex after = commands.getSelectedCommands(); if (after == null) { return Futures.immediateFailedFuture(new RuntimeException("No command selected")); @@ -130,6 +192,19 @@ public ListenableFuture loadBoundPipelines() { Service.Value::getMultiResourceData); } + + public ListenableFuture loadFramebufferAttachments() { + CommandIndex after = commands.getSelectedCommands(); + if (after == null) { + return Futures.immediateFailedFuture(new RuntimeException("No command selected")); + } + + // TODO: don't get the device via getData + return MoreFutures.transform( + client.get(framebufferAttachmentsAfter(after), getData().device), + Service.Value::getFramebufferAttachments); + } + public void updateResource(Service.Resource resource, API.ResourceData data) { CommandIndex after = commands.getSelectedCommands(); if (after == null) { @@ -156,6 +231,36 @@ protected void onUiThread(Path.Capture result) { }); } + public Service.Resource getSelectedShader() { + return selectedShader; + } + + public void selectShader(Service.Resource shader) { + if (selectedShader != shader) { + selectedShader = shader; + listeners.fire().onShaderSelected(selectedShader); + } + } + + public void pinShader(Service.Resource shader) { + listeners.fire().onShaderPinned(shader); + } + + public Service.Resource getSelectedTexture() { + return selectedTexture; + } + + public void selectTexture(Service.Resource texture) { + if (!Objects.equals(selectedTexture, texture)) { + selectedTexture = texture; + listeners.fire().onTextureSelected(selectedTexture); + } + } + + public void pinTexture(Service.Resource texture) { + listeners.fire().onTexturePinned(texture); + } + public static class Data extends DeviceDependentModel.Data { public final Service.Resources resources; @@ -164,7 +269,7 @@ public Data(Path.Device device, Service.Resources resources) { this.resources = resources; } - public ResourceList getResources(Path.Command after, API.ResourceType type) { + public ResourceList getResources(Path.Command after, Path.ResourceType type) { List list = Lists.newArrayList(); boolean complete = true; for (Service.ResourcesByType rs : resources.getTypesList()) { @@ -184,17 +289,30 @@ public ResourceList getResources(Path.Command after, API.ResourceType type) { return new ResourceList(type, list, complete); } + public Resource getResource(Path.ResourceData path) { + for (Service.ResourcesByType rs : resources.getTypesList()) { + for (Service.Resource r : rs.getResourcesList()) { + if (path.getID().equals(r.getID())) { + Path.Command deleted = r.getDeleted(); + return new Resource(r, !isNull(deleted) && compare(deleted, path.getAfter()) <= 0); + } + } + } + + return null; + } + private static Path.Command firstAccess(Service.Resource info) { return (info.getAccessesCount() == 0) ? null : info.getAccesses(0); } } public static class ResourceList { - public final API.ResourceType type; + public final Path.ResourceType type; public final List resources; public final boolean complete; - public ResourceList(API.ResourceType type, List resources, boolean complete) { + public ResourceList(Path.ResourceType type, List resources, boolean complete) { this.type = type; this.resources = resources; this.complete = complete; @@ -219,10 +337,31 @@ public Resource(Service.Resource resource, boolean deleted) { } } + @SuppressWarnings("unused") public static interface Listener extends Events.Listener { /** * Event indicating that the resources metadata has been loaded. */ public default void onResourcesLoaded() { /* empty */ } + + /** + * Event indicating that a shader resource has been selected. + */ + public default void onShaderSelected(Service.Resource shader) { /* empty */ } + + /** + * Event indicating that a shader has been pinned. + */ + public default void onShaderPinned(Service.Resource shader) { /* empty */ } + + /** + * Event indicating that a texture resource has been selected. + */ + public default void onTextureSelected(Service.Resource texture) { /* empty */ } + + /** + * Event indicating that a texture has been pinned. + */ + public default void onTexturePinned(Service.Resource texture) { /* empty */ } } } diff --git a/gapic/src/main/com/google/gapid/models/Settings.java b/gapic/src/main/com/google/gapid/models/Settings.java index db620b3bc9..669d9f1478 100644 --- a/gapic/src/main/com/google/gapid/models/Settings.java +++ b/gapic/src/main/com/google/gapid/models/Settings.java @@ -51,41 +51,41 @@ /** * Stores various settings based on user interactions with the UI to maintain customized looks and * other shortcuts between runs. E.g. size and location of the window, directory of the last opened - * file, options for the last trace, etc. The settings are persisted in a ".gapic" file in the + * file, options for the last trace, etc. The settings are persisted in a ".agic" file in the * user's home directory. */ public class Settings { private static final Logger LOG = Logger.getLogger(Settings.class.getName()); - private static final String SETTINGS_FILE = ".gapic"; + private static final String SETTINGS_FILE = ".agic"; private static final int MAX_RECENT_FILES = 16; - private static final int CURRENT_VERSION = 1; + private static final int CURRENT_VERSION = 5; // Only set values for fields where the proto zero/false/empty default doesn't make sense. private static SettingsProto.Settings DEFAULT_SETTINGS = SettingsProto.Settings.newBuilder() .setVersion(CURRENT_VERSION) .setTabs(SettingsProto.Tabs.newBuilder() - .setStructure("g2;f1;g3;f1;f1;f2") // See GraphicsTraceView.MainTab.getFolders(). - .addAllWeights(Arrays.asList(1, 1, 4, 1, 3, 1)) - .addAllTabs(Arrays.asList("Filmstrip", "ApiCalls", "Framebuffer", "ApiState", "Memory")) + .setStructure("g2;f1;f1;") // See GraphicsTraceView.MainTab.getFolders(). + .addAllWeights(Arrays.asList(-1, 1, 4)) + .addAllTabs(Arrays.asList("Profile", "ApiCalls")) .addAllHidden(Arrays.asList("Log"))) - .setPreferences(SettingsProto.Preferences.newBuilder() - .setCheckForUpdates(true)) .setUi(SettingsProto.UI.newBuilder() .setPerfetto(SettingsProto.UI.Perfetto.newBuilder() - .setDrawerHeight(250))) + .setDrawerHeight(250)) + .setFramebufferPicker(SettingsProto.UI.FramebufferPicker.newBuilder() + .setEnabled(true)) + .setCommandSplitterRatio(0.2)) .setTrace(SettingsProto.Trace.newBuilder() .setType("Graphics") .setGfxDuration(SettingsProto.Trace.Duration.newBuilder() .setType(SettingsProto.Trace.Duration.Type.MANUAL) .setStartFrame(100) .setStartTime(10) - .setDuration(7)) + .setDuration(1)) .setProfileDuration(SettingsProto.Trace.Duration.newBuilder() .setType(SettingsProto.Trace.Duration.Type.MANUAL) .setStartTime(10) - .setDuration(5)) - .setDisablePcs(true)) + .setDuration(5))) .setPerfetto(SettingsProto.Perfetto.newBuilder() .setCpu(SettingsProto.Perfetto.CPU.newBuilder() .setEnabled(true) @@ -96,7 +96,7 @@ public class Settings { .setSlices(true) .setCounters(true) .setCounterRate(1) - .setSurfaceFlinger(true)) + .setSurfaceFlinger(false)) .setMemory(SettingsProto.Perfetto.Memory.newBuilder() .setEnabled(true) .setRate(10)) @@ -119,6 +119,7 @@ private Settings(SettingsProto.Settings.Builder proto) { this.proto = fixup(proto); } + @SuppressWarnings("deprecation") private static SettingsProto.Settings.Builder fixup(SettingsProto.Settings.Builder proto) { tryFindAdb(proto.getPreferencesBuilder()); switch (proto.getVersion()) { @@ -132,6 +133,29 @@ private static SettingsProto.Settings.Builder fixup(SettingsProto.Settings.Build .setProfileDuration(SettingsProto.Trace.Duration.newBuilder() .setType(SettingsProto.Trace.Duration.Type.MANUAL) .setDuration(5)); + //$FALL-THROUGH$ + case 1: + // Version 2 added the DeviceValidation.ValidationEntry.last_seen field. + for (SettingsProto.DeviceValidation.ValidationEntry.Builder entry : + proto.getDeviceValidationBuilder().getValidationEntriesBuilderList()) { + entry.setLastSeen(System.currentTimeMillis()); + } + //$FALL-THROUGH$ + case 2: + // Version 3 removed the Trace.api field and expanded the Trace.Type field. + if ("Graphics".equals(proto.getTrace().getType()) && + "OpenGL on ANGLE".equals(proto.getTrace().getApi())) { + proto.getTraceBuilder().setType("ANGLE"); + } + proto.getTraceBuilder().clearApi(); + //$FALL-THROUGH$ + case 3: + // Version 4 resets the default layout after the mergin of the Performance and Command tabs. + proto.setTabs(DEFAULT_SETTINGS.getTabs()); + //$FALL-THROUGH$ + case 4: + // Version 5 added the command_splitter_ratio field. + proto.getUiBuilder().setCommandSplitterRatio(0.2); } return proto.setVersion(CURRENT_VERSION); } @@ -158,7 +182,7 @@ public static Settings load() { public void save() { File file = new File(OS.userHomeDir, SETTINGS_FILE); try (Writer writer = new FileWriter(file)) { - TextFormat.print(proto, writer); + TextFormat.printer().print(proto, writer); } catch (IOException e) { LOG.log(FINE, "IO error writing properties to " + file, e); } @@ -192,6 +216,10 @@ public SettingsProto.PerfettoOrBuilder perfetto() { return proto.getPerfettoOrBuilder(); } + public SettingsProto.FuchsiaTracingOrBuilder fuchsiaTracing() { + return proto.getFuchsiaTracingOrBuilder(); + } + public SettingsProto.Window.Builder writeWindow() { return proto.getWindowBuilder(); } @@ -220,6 +248,14 @@ public SettingsProto.Perfetto.Builder writePerfetto() { return proto.getPerfettoBuilder(); } + public SettingsProto.FuchsiaTracing.Builder writeFuchsiaTracing() { + return proto.getFuchsiaTracingBuilder(); + } + + public SettingsProto.DeviceValidation.Builder writeDeviceValidation() { + return proto.getDeviceValidationBuilder(); + } + public int[] getSplitterWeights(SplitterWeights type) { return type.get(ui()); } @@ -333,7 +369,7 @@ private static void tryFindAdb(SettingsProto.Preferences.Builder current) { return; } - String[] sdkVars = { "ANDROID_HOME", "ANDROID_SDK_HOME", "ANDROID_ROOT", "ANDROID_SDK_ROOT" }; + String[] sdkVars = { "ANDROID_HOME", "ANDROID_ROOT", "ANDROID_SDK_ROOT" }; for (String sdkVar : sdkVars) { File adb = findAdbInSdk(System.getenv(sdkVar)); if (adb != null) { @@ -359,11 +395,10 @@ private static File findAdbInSdk(String sdk) { } public static enum SplitterWeights { - Textures(new int[] { 20, 80 }), Report(new int[] { 75, 25 }), Shaders(new int[] { 20, 80 }), - Programs(new int[] { 20, 80 }), - Uniforms(new int[] { 70, 30 }); + Textures(new int[] { 30, 70 }), + Commands(new int[] { 20, 80 }); private final int[] dflt; diff --git a/gapic/src/main/com/google/gapid/models/Timeline.java b/gapic/src/main/com/google/gapid/models/Timeline.java deleted file mode 100644 index b2fd974b95..0000000000 --- a/gapic/src/main/com/google/gapid/models/Timeline.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2017 Google Inc. - * - * 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. - */ -package com.google.gapid.models; - -import static com.google.gapid.util.Paths.events; - -import com.google.common.util.concurrent.ListenableFuture; -import com.google.gapid.models.ApiContext.FilteringContext; -import com.google.gapid.proto.service.Service; -import com.google.gapid.proto.service.path.Path; -import com.google.gapid.server.Client; -import com.google.gapid.util.Events; -import com.google.gapid.util.MoreFutures; - -import org.eclipse.swt.widgets.Shell; - -import java.util.Iterator; -import java.util.List; -import java.util.logging.Logger; - -public class Timeline extends CaptureDependentModel.ForPath - implements ApiContext.Listener { - private static final Logger LOG = Logger.getLogger(Timeline.class.getName()); - - private final Capture capture; - private final ApiContext context; - - public Timeline(Shell shell, Analytics analytics, Client client, Capture capture, - Devices devices, ApiContext context) { - super(LOG, shell, analytics, client, Listener.class, capture, devices); - this.capture = capture; - this.context = context; - - context.addListener(this); - } - - @Override - public void onContextsLoaded() { - onContextSelected(context.getSelectedContext()); - } - - @Override - public void onContextSelected(FilteringContext ctx) { - load(getSource(capture.getData()), false); - } - - @Override - protected Path.Any getSource(Capture.Data data) { - FilteringContext ctx = context.isLoaded() ? context.getSelectedContext() : null; - return (ctx == null) ? null : events(data.path, ctx); - } - - @Override - protected boolean shouldLoad(Capture.Data data) { - return data.isGraphics(); - } - - @Override - protected ListenableFuture doLoad(Path.Any path, Path.Device device) { - return MoreFutures.transform( - client.get(path, device), v -> new Data(device, v.getEvents().getListList())); - } - - @Override - protected void fireLoadStartEvent() { - listeners.fire().onTimeLineLoadingStart(); - } - - @Override - protected void fireLoadedEvent() { - listeners.fire().onTimeLineLoaded(); - } - - public Iterator getEndOfFrames() { - return getData().events.stream() - .filter(e -> e.getKind() == Service.EventKind.LastInFrame) - .iterator(); - } - - public static class Data extends DeviceDependentModel.Data { - public final List events; - - public Data(Path.Device device, List events) { - super(device); - this.events = events; - } - } - - public static interface Listener extends Events.Listener { - /** - * Event indicating that the time line is being loaded. - */ - public default void onTimeLineLoadingStart() { /* empty */ } - - /** - * Event indicating that the time line has been loaded. - */ - public default void onTimeLineLoaded() { /* empty */ } - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/PerfettoConfig.java b/gapic/src/main/com/google/gapid/perfetto/PerfettoConfig.java deleted file mode 100644 index 18ad598527..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/PerfettoConfig.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ -package com.google.gapid.perfetto; - -import static java.util.logging.Level.WARNING; - -import com.google.common.collect.ImmutableList; -import com.google.gapid.server.GapiPaths; -import com.google.gapid.util.Flags; -import com.google.gapid.util.Flags.Flag; -import com.google.protobuf.TextFormat; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.util.function.Supplier; -import java.util.logging.Logger; - -public class PerfettoConfig { - public static final Flag perfettoConfig = Flags.value("perfetto", "", - "Path to a file containing a Perfetto config proto in text format. " + - "Specifying this flag will enable the System Profile UI features"); - - private static final Logger LOG = Logger.getLogger(PerfettoConfig.class.getName()); - private static final PerfettoConfig MISSING = new PerfettoConfig(null); - - private final perfetto.protos.PerfettoConfig.TraceConfig config; - - public PerfettoConfig(perfetto.protos.PerfettoConfig.TraceConfig config) { - this.config = config; - } - - // This call is expensive as it re-reads the perfetto config file, in order to enable perfetto - // configuration changes without having to restart GAPID. - public static synchronized PerfettoConfig get() { - return ImmutableList.>of( - () -> { - String path = perfettoConfig.get(); - return "".equals(path) ? null : new File(path); - }, - GapiPaths.get()::perfettoConfig - ).stream() - .map(dir -> checkForConfig(dir.get())) - .filter(PerfettoConfig::shouldUse) - .findFirst() - .orElse(MISSING); - } - - public boolean hasConfig() { - return config != null; - } - - public perfetto.protos.PerfettoConfig.TraceConfig getConfig() { - return config; - } - - private static boolean shouldUse(PerfettoConfig config) { - return config != null && config.hasConfig(); - } - - private static PerfettoConfig checkForConfig(File file) { - if (file == null || !file.isFile()) { - return null; - } - try (Reader in = new InputStreamReader(new FileInputStream(file))) { - perfetto.protos.PerfettoConfig.TraceConfig.Builder config = - perfetto.protos.PerfettoConfig.TraceConfig.newBuilder(); - TextFormat.merge(in, config); - return new PerfettoConfig(config.build()); - } catch (IOException e) { - LOG.log(WARNING, "Failed to read Perfetto config from " + file, e); - } - return null; - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/QueryViewer.java b/gapic/src/main/com/google/gapid/perfetto/QueryViewer.java index 246f80ed06..60a7d2a539 100644 --- a/gapic/src/main/com/google/gapid/perfetto/QueryViewer.java +++ b/gapic/src/main/com/google/gapid/perfetto/QueryViewer.java @@ -17,8 +17,10 @@ import static com.google.gapid.widgets.Widgets.createButton; import static com.google.gapid.widgets.Widgets.createComposite; +import static com.google.gapid.widgets.Widgets.createLabel; import static com.google.gapid.widgets.Widgets.createSpinner; import static com.google.gapid.widgets.Widgets.createTableColumn; +import static com.google.gapid.widgets.Widgets.createTableViewer; import static com.google.gapid.widgets.Widgets.packColumns; import static com.google.gapid.widgets.Widgets.withLayoutData; @@ -54,6 +56,7 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Spinner; import org.eclipse.swt.widgets.TableColumn; @@ -75,8 +78,9 @@ public class QueryViewer extends Composite protected static final Logger LOG = Logger.getLogger(QueryViewer.class.getName()); private final Models models; - private final Button run; - protected final Spinner tablePage; + private final Button run, export; + private final Spinner tablePage; + private final Label tablePageMax; private final StyledText query; protected final TableViewer table; protected final ResultContentProvider provider; @@ -92,8 +96,8 @@ public QueryViewer(Composite parent, Models models) { SashForm splitter = new SashForm(this, SWT.VERTICAL); - Composite top = createComposite(splitter, new GridLayout(1, false)); - + Composite top = withLayoutData(createComposite(splitter, new GridLayout(1, false)), + new GridData(SWT.FILL, SWT.FILL, true, true)); SourceViewer viewer = createSourceViewer(top, "select * from perfetto_tables"); query = viewer.getTextWidget(); query.setKeyBinding(ST.SELECT_ALL, ST.SELECT_ALL); @@ -105,20 +109,26 @@ public QueryViewer(Composite parent, Models models) { } }); - Composite middle = createComposite(top, new GridLayout(2, false)); - run = withLayoutData(createButton(middle, "Run", e -> exec()), - new GridData(SWT.LEFT, SWT.BOTTOM, false, false)); - withLayoutData(createButton(middle, "Export", e -> export()), - new GridData(SWT.LEFT, SWT.BOTTOM, false, false)); - tablePage = withLayoutData(createSpinner(middle, 1, 1, 1, e -> turnPage()), - new GridData(SWT.FILL, SWT.BOTTOM, false, false, 2, 1)); + Composite middle = withLayoutData(createComposite(top, new GridLayout(2, false)), + new GridData(SWT.RIGHT, SWT.BOTTOM, false, false)); + run = createButton(middle, "Run", e -> exec()); + export = createButton(middle, "Export", e -> export()); - table = Widgets.createTableViewer(splitter, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + Composite bottom = createComposite(splitter, new GridLayout(1, false)); + table = createTableViewer(bottom, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + table.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); table.setContentProvider(provider); table.setLabelProvider(new LabelProvider()); + Composite pager = withLayoutData(createComposite(bottom, new GridLayout(3, false)), + new GridData(SWT.RIGHT, SWT.BOTTOM, false, false)); + createLabel(pager, "Page:"); + tablePage = createSpinner(pager, 1, 1, 1, e -> turnPage()); + tablePageMax = createLabel(pager, "of 1"); + splitter.setWeights(new int[] { 30, 70 }); run.setEnabled(models.capture.isPerfetto()); + export.setEnabled(models.capture.isPerfetto()); models.capture.addListener(this); models.perfetto.addListener(this); @@ -136,11 +146,13 @@ private void turnPage() { @Override public void onCaptureLoadingStart(boolean maintainState) { run.setEnabled(false); + export.setEnabled(false); } @Override public void onPerfettoLoaded(Loadable.Message error) { run.setEnabled(error == null && models.capture.isPerfetto()); + export.setEnabled(error == null && models.capture.isPerfetto()); } private static SourceViewer createSourceViewer(Composite parent, String string) { @@ -162,6 +174,16 @@ private static boolean isKey(Event e, int stateMask, int keyCode) { return (e.stateMask & stateMask) == stateMask && e.keyCode == keyCode; } + protected void updatePager(Perfetto.QueryResult result) { + provider.setPage(1); + + int max = Math.max(1, ((int)result.getNumRecords() + MAX_ENTRIES - 1) / MAX_ENTRIES); + tablePage.setMaximum(max); + tablePage.setSelection(1); + tablePageMax.setText("of " + max); + tablePageMax.requestLayout(); + } + private void exec() { Rpc.listen(models.perfetto.query(query.getText()), new UiCallback(this, LOG) { @@ -180,9 +202,7 @@ protected Perfetto.QueryResult onRpcThread( @Override protected void onUiThread(Perfetto.QueryResult result) { - tablePage.setMaximum((int)result.getNumRecords() / MAX_ENTRIES - 1); - provider.setPage(1); - + updatePager(result); table.setInput(null); for (TableColumn col : table.getTable().getColumns()) { col.dispose(); @@ -206,8 +226,6 @@ protected void onUiThread(Perfetto.QueryResult result) { table.setInput(result); packColumns(table.getTable()); table.getTable().requestLayout(); - - tablePage.setSelection(1); } }); } @@ -221,12 +239,12 @@ private void export() { dialog.setFilterExtensions(filters); String fileName = dialog.open(); if (fileName != null) { - String filterExt = filters[dialog.getFilterIndex()].substring(1); - if (!fileName.substring(fileName.length()-4).equals(filterExt)) { + String filterExt = filters[Math.max(0, dialog.getFilterIndex())].substring(1); + if (!fileName.endsWith(filterExt)) { fileName += filterExt; } char separator = (filterExt.equals(".csv")) ? ',' : '\t'; - LOG.log(Level.INFO, fileName); + LOG.log(Level.FINE, "Exporting to {0}", fileName); saveQuery(new File(fileName), separator); } } @@ -319,7 +337,7 @@ protected static Comparator comparator(Perfetto.QueryResult res, int col) { } } - private static class ResultContentProvider implements IStructuredContentProvider { + public static class ResultContentProvider implements IStructuredContentProvider { private int page; public ResultContentProvider() { @@ -350,7 +368,7 @@ public Object[] getElements(Object inputElement) { } } - private static class Row { + public static class Row { public final Perfetto.QueryResult result; public final int row; diff --git a/gapic/src/main/com/google/gapid/perfetto/ThreadState.java b/gapic/src/main/com/google/gapid/perfetto/ThreadState.java index 5cfaa5777d..3eb8e7f1c1 100644 --- a/gapic/src/main/com/google/gapid/perfetto/ThreadState.java +++ b/gapic/src/main/com/google/gapid/perfetto/ThreadState.java @@ -90,6 +90,7 @@ public static ThreadState of(String state) { case "K": return WAKE_KILL; case "r": + case "Running": return RUNNING; case "R": case "R+": diff --git a/gapic/src/main/com/google/gapid/perfetto/TimeSpan.java b/gapic/src/main/com/google/gapid/perfetto/TimeSpan.java index 7acc92e6d1..7f9ac3d607 100644 --- a/gapic/src/main/com/google/gapid/perfetto/TimeSpan.java +++ b/gapic/src/main/com/google/gapid/perfetto/TimeSpan.java @@ -50,7 +50,7 @@ public TimeSpan expand(TimeSpan other) { public final long end; public TimeSpan(long startNs, long endNs) { - checkArgument(startNs <= endNs, "%d > %d", startNs, endNs); + checkArgument(startNs <= endNs, "%s > %s", startNs, endNs); this.start = startNs; this.end = endNs; } @@ -77,6 +77,10 @@ public boolean contains(TimeSpan other) { return start <= other.start && end >= other.end; } + public boolean overlaps(long oStart, long oEnd) { + return start < oEnd && oStart < end; + } + public TimeSpan expand(long deltaNs) { return expand(deltaNs, deltaNs); } diff --git a/gapic/src/main/com/google/gapid/perfetto/TraceView.java b/gapic/src/main/com/google/gapid/perfetto/TraceView.java index 1fd5aef810..20e7d26a33 100644 --- a/gapic/src/main/com/google/gapid/perfetto/TraceView.java +++ b/gapic/src/main/com/google/gapid/perfetto/TraceView.java @@ -72,7 +72,8 @@ public TraceView(Composite parent, Models models, Widgets widgets) { private static TraceComposite createTraceUi( Composite parent, Models models, Theme theme) { - return new TraceComposite(parent, models.analytics, theme) { + return new TraceComposite( + parent, models.analytics, models.perfetto, theme, /*fullView*/ true) { @Override protected State.ForSystemTrace createState() { return new State.ForSystemTrace(this); diff --git a/gapic/src/main/com/google/gapid/perfetto/Unit.java b/gapic/src/main/com/google/gapid/perfetto/Unit.java index 6f3602c3cb..bf9013508b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/Unit.java +++ b/gapic/src/main/com/google/gapid/perfetto/Unit.java @@ -15,12 +15,14 @@ */ package com.google.gapid.perfetto; +import static java.lang.Math.pow; + import java.util.Arrays; import java.util.stream.Collectors; public class Unit { private static final String[] BIT_NAMES = { "bits", "Kbit", "Mbit", "Gbit", "Tbit", "Pbit" }; - private static final String[] BYTE_NAMES = { "bytes", "KB", "MB", "GB", "TB", "PB" }; + private static final String[] BYTE_NAMES = { "B", "KB", "MB", "GB", "TB", "PB" }; private static final String[] HERTZ_NAMES = { "Hz", "KHz", "MHz", "GHz", "THz", "PHz" }; private static final String[] WATT_NAMES = { "mW", "W", "KW" }; @@ -33,7 +35,7 @@ public class Unit { public static final Unit TERA_BIT = new Unit("Tbit", new Formatter.Base2(BIT_NAMES, 4)); public static final Unit PETA_BIT = new Unit("Pbit", new Formatter.Base2(BIT_NAMES, 5)); - public static final Unit BYTE = new Unit("bytes", new Formatter.Base2(BYTE_NAMES, 0)); + public static final Unit BYTE = new Unit("B", new Formatter.Base2(BYTE_NAMES, 0)); public static final Unit KILO_BYTE = new Unit("KB", new Formatter.Base2(BYTE_NAMES, 1)); public static final Unit MEGA_BYTE = new Unit("MB", new Formatter.Base2(BYTE_NAMES, 2)); public static final Unit GIGA_BYTE = new Unit("GB", new Formatter.Base2(BYTE_NAMES, 3)); @@ -72,7 +74,7 @@ public class Unit { public static final Unit FAHRENHEIT = new Unit("F", new Formatter.Simple("F")); public static final Unit KELVIN = new Unit("K", new Formatter.Simple("K")); - public static final Unit PERCENT = new Unit("%", new Formatter.Simple("%")); + public static final Unit PERCENT = new Unit("%", new Formatter.Percent()); public static final Unit INSTRUCTION = new Unit("instr", new Formatter.Simple("instr")); @@ -106,6 +108,22 @@ public String format(long value) { public String format(double value) { return numer.format(value) + "/" + denom.name; } + + @Override + public Formatter withFixedScale(double representativeValue) { + Unit fixed = numer.withFixedScale(representativeValue); + return new Formatter() { + @Override + public String format(long value) { + return fixed.format(value) + "/" + denom.name; + } + + @Override + public String format(double value) { + return fixed.format(value) + "/" + denom.name; + } + }; + } }); } @@ -144,6 +162,8 @@ public String format(long value) { public String format(double value) { if (Double.isNaN(value) || Double.isInfinite(value)) { return (Double.toString(value) + " " + name).trim(); + } else if (value == 0) { + return formatter.format(0); } double abs = Math.abs(value); @@ -154,9 +174,20 @@ public String format(double value) { } } + public Unit withFixedScale(double representativeValue) { + return new Unit(name, formatter.withFixedScale(representativeValue)); + } + + public static String bytesToString(long val) { + return Unit.BYTE.format(val); + } + private static interface Formatter { public String format(long value); public String format(double value); + // Return a Formatter that format values with a fixed scale, rather than deciding the scale + // every time based on the coming value. The fixed scale is picked based on representativeValue. + public default Formatter withFixedScale(double representativeValue) { return this; } public static final Formatter DEFAULT = new Formatter() { @Override @@ -188,6 +219,23 @@ public String format(double value) { } } + public static class Percent extends Simple { + private static final double CUT_OFF = 0.05; + + public Percent() { + super("%"); + } + + @Override + public String format(double value) { + if (value == 100) { + return super.format(100); + } else { + return value < CUT_OFF ? String.format("%,g%%", value) : String.format("%,.2f%%", value); + } + } + } + public static class Base2 implements Formatter { private static final int BASE = 1024; private static final int CUT_OVER = 1536; // 1.5 * BASE @@ -247,6 +295,32 @@ public String format(double value) { return String.format("%,d.%03d%s", (long)(value / BASE), Math.round((value % BASE) * 1000 / BASE), names[unit]); } + + @Override + public Formatter withFixedScale(double representativeValue) { + double bestConvertedValue = representativeValue; + int bestOffset = this.offset; + for (int offset = 0; offset < names.length; offset++) { + double convertedValue = representativeValue * pow(BASE, this.offset - offset); + // Wish the converted value will be larger than 1, but as small as possible. + if (convertedValue > 1 && convertedValue < bestConvertedValue) { + bestConvertedValue = convertedValue; + bestOffset = offset; + } + } + int fromOffset = this.offset,toOffset = bestOffset; + return new Formatter() { + @Override + public String format(long value) { + return String.format("%,.3f %s", value * pow(BASE, fromOffset - toOffset), names[toOffset]); + } + + @Override + public String format(double value) { + return String.format("%,.3f %s", value * pow(BASE, fromOffset - toOffset), names[toOffset]); + } + }; + } } public static class Base10 implements Formatter { @@ -305,6 +379,32 @@ public String format(double value) { return String.format( "%,d.%03d%s", (long)(value / BASE), Math.round(value % BASE), names[unit]); } + + @Override + public Formatter withFixedScale(double representativeValue) { + double bestConvertedValue = representativeValue; + int bestOffset = this.offset; + for (int offset = 0; offset < names.length; offset++) { + double convertedValue = representativeValue * pow(BASE, this.offset - offset); + // Wish the converted value will be larger than 1, but as small as possible. + if (convertedValue > 1 && convertedValue < bestConvertedValue) { + bestConvertedValue = convertedValue; + bestOffset = offset; + } + } + int fromOffset = this.offset, toOffset = bestOffset; + return new Formatter() { + @Override + public String format(long value) { + return String.format("%,.3f %s", value * pow(BASE, fromOffset - toOffset), names[toOffset]); + } + + @Override + public String format(double value) { + return String.format("%,.3f %s", value * pow(BASE, fromOffset - toOffset), names[toOffset]); + } + }; + } } public static class Time implements Formatter { @@ -315,6 +415,14 @@ public static class Time implements Formatter { private static final int SECONDS = 3; private static final String[] NAMES = { "ns", "us", "ms", "s", "m", "h" }; + private static final double[][] TIME_CONVERT_TABLE = { + {1, 1f / 1000, 1f/pow(1000, 2), 1f/pow(1000, 3), 1f/(pow(1000, 3)* 60), 1f/(pow(1000, 3)* 60 * 60)}, // ns -> {ns, us, ms, s, m, h} + {1000, 1, 1f / 1000, 1f/pow(1000, 2), 1f/(pow(1000, 2)* 60), 1f/(pow(1000, 2)* 60 * 60)}, // us -> {ns, us, ms, s, m, h} + {pow(1000, 2), 1000, 1, 1f / 1000, 1f/(1000* 60), 1f/(1000 * 60 * 60)}, // ms -> {ns, us, ms, s, m, h} + {pow(1000, 3), pow(1000, 2), 1000, 1, 1f/60, 1f/(60 * 60)}, // s -> {ns, us, ms, s, m, h} + {pow(1000, 3) * 60, pow(1000, 2) * 60, 1000 * 60, 60, 1, 1f/60}, // m -> {ns, us, ms, s, m, h} + {pow(1000, 3) * 60 * 60, pow(1000, 2) * 60 * 60, 1000 * 60 * 60, 60 * 60, 60, 1}, // h -> {ns, us, ms, s, m, h} + }; private final int offset; private Time(int offset) { @@ -436,6 +544,34 @@ public String format(double value) { return String.format("%,gh", value); } } + + @Override + public Formatter withFixedScale(double representativeValue) { + double bestConvertedValue = representativeValue; + int bestOffset = this.offset; + for (int offset = 0; offset < NAMES.length; offset++) { + double convertedValue = representativeValue * TIME_CONVERT_TABLE[this.offset][offset]; + // Wish the converted value will be larger than 1, but as small as possible. + if (convertedValue > 1 && convertedValue < bestConvertedValue) { + bestConvertedValue = convertedValue; + bestOffset = offset; + } + } + int fromOffset = this.offset, toOffset = bestOffset; + return new Formatter() { + @Override + public String format(long value) { + return String.format("%,.3f %s", value * TIME_CONVERT_TABLE[fromOffset][toOffset], + NAMES[toOffset]); + } + + @Override + public String format(double value) { + return String.format("%,.3f %s", value * TIME_CONVERT_TABLE[fromOffset][toOffset], + NAMES[toOffset]); + } + }; + } } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/Area.java b/gapic/src/main/com/google/gapid/perfetto/canvas/Area.java index 4fc0bcad9c..665700210e 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/Area.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/Area.java @@ -29,6 +29,11 @@ public Area translate(double dx, double dy) { return NONE; } + @Override + public Area shrink(double dx, double dy) { + return NONE; + } + @Override public Area combine(Area o) { return o; @@ -76,6 +81,11 @@ public Area translate(double dx, double dy) { return FULL; } + @Override + public Area shrink(double dx, double dy) { + return FULL; + } + @Override public Area combine(Area o) { return FULL; @@ -134,6 +144,18 @@ public Area translate(double dx, double dy) { return new Area(x + dx, y + dy, w, h); } + // shrinks the area by dx on the left and right side, and by dy from top and bottom. + public Area shrink(double dx, double dy) { + if (2 * dx >= w || 2 * dy >= h) { + return Area.NONE; + } + return new Area(x + dx, y + dy, w - 2 * dx, h - 2 * dy); + } + + public boolean contains(double px, double py) { + return px >= x && px < x + w && py >= y && py < y + h; + } + public Area combine(Area o) { if (o.isEmpty()) { return this; diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/Fonts.java b/gapic/src/main/com/google/gapid/perfetto/canvas/Fonts.java index 61b272a21c..bd5d049e1d 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/Fonts.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/Fonts.java @@ -21,11 +21,13 @@ import com.google.common.cache.CacheBuilder; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Device; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.FontMetrics; import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.widgets.Control; import java.util.concurrent.ExecutionException; @@ -44,11 +46,18 @@ public static enum Style { public static interface TextMeasurer { public Size measure(Style style, String text); + public Size measure(TextLayout layout); public double getAscent(Style style); public double getDescent(Style style); + public int getOffset(TextLayout layout, double x, double y); } - public static class Context implements TextMeasurer { + public static interface FontContext { + public TextLayout newTextLayout(); + public void applyStyle(TextLayout layout, StyleRange range); + } + + public static class Context implements TextMeasurer, FontContext { private final FontAndGC[] fonts = new FontAndGC[Style.values().length]; private final Cache textExtentCache = CacheBuilder.newBuilder() .softValues() @@ -70,6 +79,11 @@ public Size measure(Style style, String text) { } } + @Override + public Size measure(TextLayout layout) { + return Size.of(layout.getBounds(), 1 / scale); + } + @Override public double getAscent(Style style) { return fonts[style.ordinal()].getAscent(); @@ -80,6 +94,24 @@ public double getDescent(Style style) { return fonts[style.ordinal()].getDescent(); } + @Override + public int getOffset(TextLayout layout, double x, double y) { + return layout.getOffset(scale(x), scale(y), null); + } + + @Override + public TextLayout newTextLayout() { + return fonts[Style.Normal.ordinal()].newTextLayout(); + } + + @Override + public void applyStyle(TextLayout layout, StyleRange style) { + if (style.font == null && style.fontStyle == SWT.BOLD) { + fonts[Style.Bold.ordinal()].updateStyle(style); + } + layout.setStyle(style, style.start, style.start + style.length - 1); + } + public void setFont(GC gc, Style style) { fonts[style.ordinal()].apply(gc); } @@ -114,7 +146,7 @@ public static FontAndGC get(Control owner, int style) { } public Size measure(String text) { - return Size.of(gc.textExtent(text, SWT.DRAW_TRANSPARENT), 1 / scale); + return Size.of(gc.textExtent(text, SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER), 1 / scale); } public double getAscent() { @@ -129,8 +161,19 @@ public void apply(GC target) { target.setFont(font); } + public TextLayout newTextLayout() { + TextLayout layout = new TextLayout(font.getDevice()); + layout.setFont(font); + return layout; + } + + public void updateStyle(StyleRange style) { + style.font = font; + } + public void dispose() { font.dispose(); + gc.dispose(); } private static Font scaleFont(Device display, Font font, int style) { diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/Panel.java b/gapic/src/main/com/google/gapid/perfetto/canvas/Panel.java index 122e3136c0..4ab7a110b5 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/Panel.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/Panel.java @@ -37,7 +37,8 @@ public default Panel.Dragger onDragStart(double x, double y, int mods) { } @SuppressWarnings("unused") - public default Panel.Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { + public default Panel.Hover onMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { return Hover.NONE; } @@ -135,6 +136,7 @@ public default void stop() { /* empty */ } /** Returns whether the screen should be redrawn. */ public default boolean click() { return false; } + public default boolean rightClick() { return click(); } public default Panel.Hover translated(double dx, double dy) { return transformed(a -> a.translate(dx, dy)); @@ -167,6 +169,11 @@ public boolean isOverlay() { public boolean click() { return Hover.this.click(); } + + @Override + public boolean rightClick() { + return Hover.this.rightClick(); + } }; } @@ -197,6 +204,12 @@ public boolean click() { boolean r1 = Hover.this.click(), r2 = onClick.getAsBoolean(); return r1 || r2; } + + @Override + public boolean rightClick() { + boolean r1 = Hover.this.rightClick(), r2 = onClick.getAsBoolean(); + return r1 || r2; + } }; } @@ -226,6 +239,11 @@ public boolean isOverlay() { public boolean click() { return Hover.this.click(); } + + @Override + public boolean rightClick() { + return Hover.this.rightClick(); + } }; } } @@ -247,6 +265,40 @@ public static Visitor of(Class cls, BiConsumer c) { } } + public static interface Grouper { + public int getPanelCount(); + public Panel getPanel(int idx); + public void setVisible(int idx, boolean visible); + public void setFiltered(int idx, boolean filtered); + + public default boolean updateFilter(Function include) { + boolean found = false; + for (int i = 0; i < getPanelCount(); i++) { + Panel panel = getPanel(i); + FilterValue filter = include.apply(panel); + boolean keep = true; + if (panel instanceof Grouper) { + if (filter == FilterValue.Include) { + ((Grouper)panel).updateFilter($ -> FilterValue.Include); + } else { + keep = ((Grouper)panel).updateFilter(include); + } + } else if (filter == FilterValue.DescendOrExclude) { + keep = false; + } + setFiltered(i, !keep); + found |= keep; + } + return found; + } + } + + public static enum FilterValue { + Include, // Include this panel and if it's a group, all its children. + DescendOrInclude, // If this panel is a group then recursively filter, otherwise include it. + DescendOrExclude; // If this panel is a group then recursively filter, otherwise exclude it. + } + public abstract static class Base implements Panel { protected double width; protected double height; diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/PanelCanvas.java b/gapic/src/main/com/google/gapid/perfetto/canvas/PanelCanvas.java index 30802d18b2..db48615b15 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/PanelCanvas.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/PanelCanvas.java @@ -99,6 +99,10 @@ public PanelCanvas(Composite parent, int style, Theme theme, Panel panel) { redraw(dragger.onDragEnd(e.x, e.y), false); dragger = Panel.Dragger.NONE; updateMousePosition(e.x, e.y, 0, false, true); + } else if (e.button == 3) { + if (hover.rightClick()) { + structureHasChanged(); + } } else if (hover.click()) { structureHasChanged(); } @@ -142,7 +146,8 @@ private void updateMousePosition(int x, int y, int mods, boolean force, boolean } return; } - hover = panel.onMouseMove(context, x, y, mods); + hover = panel.onMouseMove( + context, a -> scheduleIfNotDisposed(this, () -> redraw(a, true)), x, y, mods); if (!dragging) { setCursor(hover.getCursor(getDisplay())); } diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/PanelGroup.java b/gapic/src/main/com/google/gapid/perfetto/canvas/PanelGroup.java index 16bc00ae85..b28542ec01 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/PanelGroup.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/PanelGroup.java @@ -23,16 +23,18 @@ /** * Contains a list of child {@link Panel panels}, which are laid out vertically. */ -public class PanelGroup extends Panel.Base { +public class PanelGroup extends Panel.Base implements Panel.Grouper { private final List panels = Lists.newArrayList(); public PanelGroup() { } - public int size() { + @Override + public int getPanelCount() { return panels.size(); } + @Override public Panel getPanel(int idx) { return panels.get(idx).panel; } @@ -65,18 +67,24 @@ public void clear() { } public boolean isVisible(int idx) { - return panels.get(idx).visible; + return panels.get(idx).isVisible(); } + @Override public void setVisible(int idx, boolean visible) { - panels.get(idx).visible = visible; + panels.get(idx).setVisibile(visible); + } + + @Override + public void setFiltered(int idx, boolean include) { + panels.get(idx).setFiltered(include); } @Override public double getPreferredHeight() { double y = 0; for (Child child : panels) { - double want = child.visible ? child.panel.getPreferredHeight() : 0; + double want = child.isVisible() ? child.panel.getPreferredHeight() : 0; child.y = y; child.h = want; y += want; @@ -88,7 +96,7 @@ public double getPreferredHeight() { public void setSize(double w, double h) { super.setSize(w, h); for (PanelGroup.Child child : panels) { - if (child.visible) { + if (child.isVisible()) { child.panel.setSize(w, child.h); } } @@ -101,7 +109,7 @@ public void render(RenderContext ctx, Repainter repainter) { for (int i = first; i < panels.size(); i++) { PanelGroup.Child child = panels.get(i); - if (!child.visible) { + if (!child.isVisible()) { continue; } @@ -122,7 +130,7 @@ public void visit(Visitor v, Area area) { int first = findPanelIdx(area.y); for (int i = first; i < panels.size(); i++) { PanelGroup.Child child = panels.get(i); - if (!child.visible) { + if (!child.isVisible()) { continue; } area.intersect(0, child.y, width, child.h) @@ -140,17 +148,18 @@ public Dragger onDragStart(double x, double y, int mods) { } @Override - public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { + public Hover onMouseMove(Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { Child child = findPanel(y); if (child == null) { return Hover.NONE; } - return child.panel.onMouseMove(m, x, y - child.y, mods).translated(0, child.y); + return child.panel.onMouseMove(m, repainter.translated(0, child.y), x, y - child.y, mods) + .translated(0, child.y); } private int findPanelIdx(double y) { int first = Collections.binarySearch(panels, null, (c1, ign) -> { - if (c1.visible) { + if (c1.isVisible()) { return (y >= c1.y && y < c1.getNextY()) ? 0 : Double.compare(c1.y, y); } else { return c1.y < y ? -1 : 1; @@ -167,17 +176,31 @@ private Child findPanel(double y) { private static class Child { public final Panel panel; public double y, h; - public boolean visible; + private boolean visible; + private boolean filtered; public Child(Panel panel, double y, double h) { this.panel = panel; this.y = y; this.h = Math.max(0, h); this.visible = true; + this.filtered = false; } public double getNextY() { return y + h; } + + public void setVisibile(boolean visible) { + this.visible = visible; + } + + public void setFiltered(boolean filtered) { + this.filtered = filtered; + } + + public boolean isVisible() { + return visible && !filtered; + } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/RenderContext.java b/gapic/src/main/com/google/gapid/perfetto/canvas/RenderContext.java index 55e5c178b1..e087d28e2b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/RenderContext.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/RenderContext.java @@ -22,11 +22,13 @@ import com.google.gapid.widgets.Theme; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGBA; import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.graphics.Transform; import org.eclipse.swt.internal.DPIUtil; import org.eclipse.swt.widgets.Control; @@ -40,7 +42,7 @@ /** * Handles all the drawing operations. */ -public class RenderContext implements Fonts.TextMeasurer, AutoCloseable { +public class RenderContext implements Fonts.TextMeasurer, Fonts.FontContext, AutoCloseable { protected static final Logger LOG = Logger.getLogger(RenderContext.class.getName()); private static final int textSizeGreediness = 3; // must be >= 1. @@ -97,6 +99,11 @@ public Size measure(Fonts.Style style, String text) { return fontContext.measure(style, text); } + @Override + public Size measure(TextLayout layout) { + return fontContext.measure(layout); + } + @Override public double getAscent(Style style) { return fontContext.getAscent(style); @@ -107,6 +114,21 @@ public double getDescent(Style style) { return fontContext.getDescent(style); } + @Override + public int getOffset(TextLayout layout, double x, double y) { + return fontContext.getOffset(layout, x, y); + } + + @Override + public TextLayout newTextLayout() { + return null; + } + + @Override + public void applyStyle(TextLayout layout, StyleRange range) { + fontContext.applyStyle(layout, range); + } + @Override public void close() { Preconditions.checkState( @@ -203,7 +225,7 @@ public void drawText(Fonts.Style style, String text, double x, double y) { lastFontStyle = style; fontContext.setFont(gc, style); } - gc.drawText(text, scale(x), scale(y), SWT.DRAW_TRANSPARENT); + gc.drawText(text, scale(x), scale(y), SWT.DRAW_TRANSPARENT | SWT.DRAW_DELIMITER); } // draws text centered vertically @@ -284,16 +306,75 @@ public void drawTextLeftTruncate( } } + // draws text centered vertically, right truncated to fit into the given width. + public void drawTextRightTruncate( + Fonts.Style style, String text, double x, double y, double w, double h) { + drawTextRightTruncate(style, text, x, y, w, h, false); + } + + // draws text centered horizontally and vertically, right truncated to fit into the given width. + public void drawTextCenteredRightTruncate( + Fonts.Style style, String text, double x, double y, double w, double h) { + drawTextRightTruncate(style, text, x, y, w, h, true); + } + + private void drawTextRightTruncate( + Fonts.Style style, String text, double x, double y, double w, double h, boolean centered) { + String toDisplay = text; + for (int l = text.length(); ; ) { + Size size = fontContext.measure(style, toDisplay); + if (size.w < w) { + drawText(style, toDisplay, x + (centered ? (w - size.w) / 2 : 0), y + (h - size.h) / 2); + break; + } + + l = Math.min(l - textSizeGreediness, (int)(w / (size.w / toDisplay.length()))); + if (l <= 0) { + break; + } + toDisplay = text.substring(0, l) + "..."; + } + } + + public void drawTextTruncate( + Fonts.Style style, String text, double x, double y, double w, double h, boolean rightTruncate) { + if (rightTruncate) { + drawTextRightTruncate(style, text, x, y, w, h); + } else { + drawTextLeftTruncate(style, text, x, y, w, h); + } + } + + // draws the text on the left of x, and beneath the bottom of y. + public void drawTextRightJustified(Fonts.Style style, String text, double x, double y) { + Size size = fontContext.measure(style, text); + drawText(style, text, x - size.w, y); + } + // draws the text centered vertically and on the left of x. public void drawTextRightJustified(Fonts.Style style, String text, double x, double y, double h) { Size size = fontContext.measure(style, text); drawText(style, text, x - size.w, y + (h - size.h) / 2); } + public void drawText(TextLayout text, double x, double y) { + text.draw(gc, scale(x), scale(y)); + } + public void drawPath(Path path) { gc.drawPath(path.path); } + public void drawPath(Path path, double lineWidthScale) { + int lineWidth = gc.getLineWidth(); + try { + gc.setLineWidth((int)(lineWidth * lineWidthScale)); + gc.drawPath(path.path); + } finally { + gc.setLineWidth(lineWidth); + } + } + public void fillPath(Path path) { gc.fillPath(path.path); } @@ -325,6 +406,11 @@ public Area getClip() { } public void withTranslation(double x, double y, Runnable run) { + if (x == 0 && y == 0) { + run.run(); + return; + } + Area clip = getClip().translate(-x, -y); Transform transform = new Transform(gc.getDevice()); try { @@ -356,6 +442,44 @@ public void withClip(double x, double y, double w, double h, Runnable run) { gc.setClipping(rect(old.x, old.y, old.w, old.h)); } + // This is the same as a sequence of withClip and withTranslation. + public void withClipAndTranslation(double clipX, double clipY, double clipW, double clipH, + double translateX, double translateY, Runnable run) { + Area old = getClip(); + Area clip = old.intersect(clipX, clipY, clipW, clipH); + if (clip.isEmpty()) { + return; + } + + Transform transform = new Transform(gc.getDevice()); + try { + gc.getTransform(transform); + transform.translate((float)(translateX * scale), (float)(translateY * scale)); + + gc.setClipping(rect(clip.x, clip.y, clip.w, clip.h)); + gc.setTransform(transform); + transformStack.add(new TransformAndClip(transform, clip.translate(-translateX, -translateY))); + run.run(); + transformStack.removeLast(); + gc.setTransform(transformStack.getLast().transform); + gc.setClipping(rect(old.x, old.y, old.w, old.h)); + } finally { + transform.dispose(); + } + } + + // This is the same as withClip and withTranslation, where the transform puts the origin at clip.xy. + public void withClipAndTranslation(double x, double y, double w, double h, Runnable run) { + withClipAndTranslation(x, y, w, h, x, y, run); + } + + // This is the same as withClip and withTranslation, where the transform puts the origin at clip.xy. + public void withClipAndTranslation(Area a, Runnable run) { + if (!a.isEmpty()) { + withClipAndTranslation(a.x, a.y, a.w, a.h, run); + } + } + public void trace(String label, Runnable run) { long start = System.nanoTime(); try { @@ -403,7 +527,7 @@ public void close() { } } - public static class Global implements Fonts.TextMeasurer { + public static class Global implements Fonts.TextMeasurer, Fonts.FontContext { private final Theme theme; private final ColorCache colors; private final Fonts.Context fontContext; @@ -427,6 +551,11 @@ public Size measure(Style style, String text) { return fontContext.measure(style, text); } + @Override + public Size measure(TextLayout layout) { + return fontContext.measure(layout); + } + @Override public double getAscent(Style style) { return fontContext.getAscent(style); @@ -437,6 +566,21 @@ public double getDescent(Style style) { return fontContext.getDescent(style); } + @Override + public int getOffset(TextLayout layout, double x, double y) { + return fontContext.getOffset(layout, x, y); + } + + @Override + public TextLayout newTextLayout() { + return fontContext.newTextLayout(); + } + + @Override + public void applyStyle(TextLayout layout, StyleRange range) { + fontContext.applyStyle(layout, range); + } + public void dispose() { colors.dispose(); fontContext.dispose(); diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/Size.java b/gapic/src/main/com/google/gapid/perfetto/canvas/Size.java index fefc12f806..c717bae1ec 100644 --- a/gapic/src/main/com/google/gapid/perfetto/canvas/Size.java +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/Size.java @@ -16,6 +16,7 @@ package com.google.gapid.perfetto.canvas; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; /** * Represents a width, height tuple. @@ -43,6 +44,14 @@ public static Size of(Point p, double scale) { return new Size(p.x * scale, p.y * scale); } + public static Size of(Rectangle rect) { + return new Size(rect.width, rect.height); + } + + public static Size of(Rectangle rect, double scale) { + return new Size(rect.width * scale, rect.height * scale); + } + public static Size vertCombine(double margin, double padding, Size... sizes) { double w = 0, h = 0; for (Size size : sizes) { diff --git a/gapic/src/main/com/google/gapid/perfetto/canvas/Tooltip.java b/gapic/src/main/com/google/gapid/perfetto/canvas/Tooltip.java new file mode 100644 index 0000000000..fec71f0751 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/canvas/Tooltip.java @@ -0,0 +1,247 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.canvas; + +import static com.google.common.base.CharMatcher.whitespace; +import static com.google.gapid.perfetto.views.StyleConstants.colors; + +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; + +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.Rectangle; + +import java.util.List; + +public abstract class Tooltip { + private static final Splitter LINE_SPLITTER = + Splitter.on(CharMatcher.anyOf("\r\n")).omitEmptyStrings().trimResults(); + private static final int MAX_WIDTH = 400; + private static final double PADDING = 4; + + private final Area area; + + public Tooltip(Area area) { + this.area = area; + } + + public Area getArea() { + return area; + } + + public void render(RenderContext ctx) { + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(area.x, area.y, area.w, area.h); + ctx.setForegroundColor(colors().panelBorder); + ctx.drawRect(area.x, area.y, area.w - 1, area.h - 1); + + ctx.setForegroundColor(colors().textMain); + ctx.withTranslation(area.x + PADDING, area.y + PADDING, () -> renderContents(ctx)); + } + + protected abstract void renderContents(RenderContext ctx); + + public static Tooltip forText(Fonts.TextMeasurer m, String text, LocationComputer lc) { + TextBuilder builder = new TextBuilder(m.measure(Fonts.Style.Normal, " ")); + for (String paragraph : LINE_SPLITTER.split(text)) { + Fonts.Style style = Fonts.Style.Normal; + if (paragraph.startsWith("\\b")) { + style = Fonts.Style.Bold; + paragraph = paragraph.substring(2); + } + builder.addParagraph(m, paragraph, style); + } + return builder.build(lc); + } + + public static Tooltip forFormattedText(Fonts.TextMeasurer m, String text, LocationComputer lc) { + TextBuilder builder = new TextBuilder(Size.ZERO); + for (String line : LINE_SPLITTER.split(text)) { + Fonts.Style style = Fonts.Style.Normal; + if (line.startsWith("\\b")) { + style = Fonts.Style.Bold; + line = line.substring(2); + } + builder.addLine(line, style, m.measure(style, line), false); + } + return builder.build(lc); + } + + public static Tooltip forImage(Image image, LocationComputer lc) { + Rectangle bounds = image.getBounds(); + return new Tooltip(lc.getLocation(bounds.width + 2 * PADDING, bounds.height + 2 * PADDING)) { + @Override + protected void renderContents(RenderContext ctx) { + ctx.drawImage(image, 0, 0); + } + }; + } + + public interface LocationComputer { + public Area getLocation(double w, double h); + + public static LocationComputer fixedLocation(double x, double y) { + return (w, h) -> new Area(x, y, w, h); + } + + public static LocationComputer standardTooltip(double x, double y, Area area) { + return (w, h) -> { + double endX = Math.min(area.x + area.w, x + w); + double startX = Math.max(area.x, endX - w); + double endY = Math.min(area.y + area.h, y + h); + double startY = Math.max(area.y, endY - h); + return new Area(startX, startY, w, h); + }; + } + + public static LocationComputer horizontallyCenteredAndConstrained( + double x, double y, double areaX, double areaWidth) { + return (w, h) -> { + double endX = Math.min(areaX + areaWidth, x + w / 2); + double startX = Math.max(areaX, endX - w); + return new Area(startX, y, w, h); + }; + } + + public static LocationComputer verticallyCenteredAndConstrained( + double x, double y, double areaY, double areaHeight) { + return (w, h) -> { + double endY = Math.min(areaY + areaHeight, y + h / 2); + double startY = Math.max(areaY, endY - h); + return new Area(x, startY, w, h); + }; + } + } + + private static class TextBuilder { + private final Size empty; + private double width = 0; + private double height = 0; + private List lines = Lists.newArrayList(); + + public TextBuilder(Size empty) { + this.empty = empty; + } + + public void addParagraph(Fonts.TextMeasurer m, String paragraph, Fonts.Style style) { + boolean first = true; + do { + Size size = m.measure(style, paragraph); + if (size.w <= MAX_WIDTH) { + addLine(paragraph, style, size, first); + return; + } + + int guess = (int)(MAX_WIDTH * paragraph.length() / size.w); + while (guess < paragraph.length() && !whitespace().matches(paragraph.charAt(guess))) { + guess++; + } + size = m.measure(style, paragraph.substring(0, guess)); + + if (size.w <= MAX_WIDTH) { + paragraph = addLineToNextSpace(m, paragraph, style, guess, size, first); + } else { + paragraph = addLineToPreviousSpace(m, paragraph, style, guess, size, first); + } + first = false; + } while (!paragraph.isEmpty()); + } + + private String addLineToNextSpace(Fonts.TextMeasurer m, String paragraph, Fonts.Style style, + int guess, Size size, boolean first) { + do { + int next = guess + 1; + while (next < paragraph.length() && !whitespace().matches(paragraph.charAt(next))) { + next++; + } + Size now = m.measure(style, paragraph.substring(0, next)); + if (now.w <= MAX_WIDTH) { + guess = next; + size = now; + } else { + break; + } + } while (guess < paragraph.length()); + addLine(paragraph.substring(0, guess), style, size, first); + return paragraph.substring(guess).trim(); + } + + private String addLineToPreviousSpace(Fonts.TextMeasurer m, String paragraph, Fonts.Style style, + int guess, Size size, boolean first) { + do { + int next = guess - 1; + while (next > 0 && !whitespace().matches(paragraph.charAt(next))) { + next--; + } + + if (next == 0) { + // We have a single word longer than our max width. Blow our limit. + addLine(paragraph.substring(0, guess), style, size, first); + return paragraph.substring(guess).trim(); + } + + guess = next; + size = m.measure(style, paragraph.substring(0, next)); + if (size.w <= MAX_WIDTH) { + addLine(paragraph.substring(0, guess), style, size, first); + return paragraph.substring(guess).trim(); + } + } while (true); + } + + public void addLine(String line, Fonts.Style style, Size size, boolean addSep) { + if (!lines.isEmpty() && addSep) { + lines.add(new Line("", style, height)); + height += empty.h; + } + lines.add(new Line(line, style, height)); + width = Math.max(width, size.w); + height += size.h; + } + + public Tooltip build(LocationComputer lc) { + double w = width + 2 * PADDING, h = height + 2 * PADDING; + Line[] myLines = lines.toArray(Line[]::new); + return new Tooltip(lc.getLocation(w, h)) { + @Override + protected void renderContents(RenderContext ctx) { + for (Line line : myLines) { + line.render(ctx); + } + } + }; + } + + private static class Line { + private final String line; + private final Fonts.Style style; + private final double y; + + public Line(String line, Fonts.Style style, double y) { + this.line = line; + this.y = y; + this.style = style; + } + + public void render(RenderContext ctx) { + if (!line.isEmpty()) { + ctx.drawText(style, line, 0, y); + } + } + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/AsyncInfo.java b/gapic/src/main/com/google/gapid/perfetto/models/AsyncInfo.java new file mode 100644 index 0000000000..3a60add826 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/AsyncInfo.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.models; + +import static com.google.gapid.util.MoreFutures.transform; + +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.Perfetto; + +import java.util.List; + +public class AsyncInfo { + private static final String LIST_SQL = + "select t.upid, t.name, t.id, max(s.depth) + 1 " + + "from process_track t inner join slice s on s.track_id = t.id " + + "group by t.upid, t.name, t.id " + + "order by t.upid, t.name, t.id"; + + public final long upid; + public final String name; + public final int maxDepth; + public final Track[] tracks; + + public AsyncInfo(long upid, String name, int maxDepth, Track[] tracks) { + this.upid = upid; + this.name = name; + this.maxDepth = maxDepth; + this.tracks = tracks; + } + + public static ListenableFuture listAsync(Perfetto.Data.Builder data) { + return transform(data.qe.query(LIST_SQL), res -> { + long groupPid = -1; + String groupName = null; + int maxDepth = 0; + List tracks = Lists.newArrayList(); + + ImmutableListMultimap.Builder asyncs = ImmutableListMultimap.builder(); + for (int i = 0; i < res.getNumRows(); i++) { + long upid = res.getLong(i, 0, 0); + String name = res.getString(i, 1, ""); + long id = res.getLong(i, 2, 0); + int depth = (int)res.getLong(i, 3, 1); + + if (upid != groupPid || !name.equals(groupName)) { + if (!tracks.isEmpty()) { + asyncs.put(groupPid, new AsyncInfo( + groupPid, groupName, maxDepth, tracks.toArray(new Track[tracks.size()]))); + } + groupPid = upid; + groupName = name; + maxDepth = 0; + tracks.clear(); + } + + tracks.add(new Track(id, maxDepth, depth)); + maxDepth += depth; + } + + if (!tracks.isEmpty()) { + asyncs.put(groupPid, new AsyncInfo( + groupPid, groupName, maxDepth, tracks.toArray(new Track[tracks.size()]))); + } + return data.setAsyncs(asyncs.build()); + }); + } + + public static class Track { + public final long trackId; + public final int depthOffset; + public final int depth; + + public Track(long trackId, int depthOffset, int depth) { + this.trackId = trackId; + this.depthOffset = depthOffset; + this.depth = depth; + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/BatterySummaryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/BatterySummaryTrack.java index 393df41333..558a36230b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/BatterySummaryTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/BatterySummaryTrack.java @@ -15,150 +15,145 @@ */ package com.google.gapid.perfetto.models; -import static com.google.gapid.perfetto.models.QueryEngine.createSpan; -import static com.google.gapid.perfetto.models.QueryEngine.createView; -import static com.google.gapid.perfetto.models.QueryEngine.createWindow; -import static com.google.gapid.perfetto.models.QueryEngine.dropTable; -import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.perfetto.models.CounterInfo.needQuantize; +import static com.google.gapid.perfetto.views.StyleConstants.POWER_RAIL_COUNTER_TRACK_HEIGHT; +import static com.google.gapid.perfetto.views.TrackContainer.group; import static com.google.gapid.perfetto.views.TrackContainer.single; -import static com.google.gapid.util.MoreFutures.transform; -import static com.google.gapid.util.MoreFutures.transformAsync; -import static java.lang.String.format; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Perfetto; +import com.google.gapid.perfetto.models.QueryEngine.Row; +import com.google.gapid.perfetto.views.BatterySelectionView; import com.google.gapid.perfetto.views.BatterySummaryPanel; +import com.google.gapid.perfetto.views.State; +import com.google.gapid.perfetto.views.TitlePanel; -public class BatterySummaryTrack extends Track.WithQueryEngine{ - private static final String VIEW_SQL = - "select ts, lead(ts) over (order by ts) - ts dur, max(a) capacity, max(b) charge," + - " max(c) current " + - "from (select ts," + - " case when track_id = %d then cast(value as int) end a," + - " case when track_id = %d then cast(value as int) end b," + - " case when track_id = %d then cast(value as int) end c " + - " from counter where track_id in (%d, %d, %d))" + - "group by ts"; - private static final String SUMMARY_SQL = - "select min(ts), max(ts + dur), cast(avg(capacity) as int), cast(avg(charge) as int)," + - " cast(avg(current) as int) " + - "from %s group by quantum_ts"; - private static final String COUNTER_SQL = - "select ts, ts + dur, capacity, charge, current from %s"; - - private final long capacityId; - private final long chargeId; - private final long currentId; +import org.eclipse.swt.widgets.Composite; + +import java.util.function.BinaryOperator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class BatterySummaryTrack + extends CombinedCountersTrack { private final double maxAbsCurrent; - private final boolean needQuantize; public BatterySummaryTrack( QueryEngine qe, CounterInfo capacity, CounterInfo charge, CounterInfo current) { - super(qe, "bat_sum"); - this.capacityId = capacity.id; - this.chargeId = charge.id; - this.currentId = current.id; - long maxCount = Math.max(capacity.count, Math.max(charge.count, current.count)); + super(qe, "bat_sum", new Column[] { + new Column(capacity.id, "int", "0", "avg"), + new Column(charge.id, "int", "0", "avg"), + new Column(current.id, "int", "0", "avg"), + }, needQuantize(capacity, charge, current)); this.maxAbsCurrent = Math.max(Math.abs(current.min), Math.abs(current.max)); - this.needQuantize = maxCount > Track.QUANTIZE_CUT_OFF; } public double getMaxAbsCurrent() { return maxAbsCurrent; } - @Override - protected ListenableFuture initialize() { - String vals = tableName("vals"); - String span = tableName("span"); - String window = tableName("window"); - return qe.queries( - dropTable(span), - dropTable(window), - dropView(vals), - createView(vals, viewSql()), - createWindow(window), - createSpan(span, vals + ", " + window)); - } + public static Perfetto.Data.Builder enumerate(Perfetto.Data.Builder data) { + ImmutableListMultimap counters = data.getCounters(CounterInfo.Type.Global); + CounterInfo battCap = onlyOne(counters.get("batt.capacity_pct")); + CounterInfo battCharge = onlyOne(counters.get("batt.charge_uah")); + CounterInfo battCurrent = onlyOne(counters.get("batt.current_ua")); - private String viewSql() { - return format(VIEW_SQL, capacityId, chargeId, currentId, capacityId, chargeId, currentId); + if ((battCap == null) || (battCharge == null) || (battCurrent == null)){ + return data; + } + // Battery Group + String batteryGroup = "battery_group"; + data.tracks.addLabelGroup(null, batteryGroup, "Battery", + group(state -> new TitlePanel("Battery"), true)); + // Battery Usage track. + if ((battCap != null) && (battCharge != null) && (battCurrent != null)) { + BatterySummaryTrack track = new BatterySummaryTrack(data.qe, battCap, battCharge, battCurrent); + data.tracks.addTrack(batteryGroup, track.getId(), "Battery Usage", + single(state -> new BatterySummaryPanel(state, track), true, false)); + } + return data; } - @Override - protected ListenableFuture computeData(DataRequest req) { - Window win = needQuantize ? Window.compute(req, 5) : Window.compute(req); - return transformAsync(win.update(qe, tableName("window")), $ -> computeData(req, win)); + private static CounterInfo onlyOne(ImmutableList counters) { + return (counters.size() != 1) ? null : counters.get(0); } - private ListenableFuture computeData(DataRequest req, Window win) { - return transform(qe.query(win.quantized ? summarySql() : counterSQL()), res -> { - int rows = res.getNumRows(); - if (rows == 0) { - return Data.empty(req); - } - - Data data = new Data( - req, new long[rows + 1], new long[rows + 1], new long[rows + 1], new long[rows + 1]); - res.forEachRow((i, r) -> { - data.ts[i] = r.getLong(0); - data.capacity[i] = r.getLong(2); - data.charge[i] = r.getLong(3); - data.current[i] = r.getLong(4); - }); - data.ts[rows] = res.getLong(rows - 1, 1, 0); - data.capacity[rows] = data.capacity[rows - 1]; - data.charge[rows] = data.charge[rows - 1]; - data.current[rows] = data.current[rows - 1]; - return data; - }); + @Override + protected Data createData(DataRequest request, int numRows) { + return new Data(request, numRows); } - private String summarySql() { - return format(SUMMARY_SQL, tableName("span")); + @Override + protected Values createValues(int numRows, BinaryOperator combiner) { + return new Values(numRows, combiner); } - private String counterSQL() { - return format(COUNTER_SQL, tableName("span")); - } + public static class Data extends CombinedCountersTrack.Data { + public final long[] capacity; + public final long[] charge; + public final long[] current; - public static Perfetto.Data.Builder enumerate(Perfetto.Data.Builder data) { - ImmutableListMultimap counters = data.getCounters(CounterInfo.Type.Global); - CounterInfo battCap = onlyOne(counters.get("batt.capacity_pct")); - CounterInfo battCharge = onlyOne(counters.get("batt.charge_uah")); - CounterInfo battCurrent = onlyOne(counters.get("batt.current_ua")); - if ((battCap == null) || (battCharge == null) || (battCurrent == null)) { - return data; + public Data(DataRequest request, int numRows) { + super(request, numRows); + this.capacity = new long[numRows]; + this.charge = new long[numRows]; + this.current = new long[numRows]; } - BatterySummaryTrack track = new BatterySummaryTrack(data.qe, battCap, battCharge, battCurrent); - data.tracks.addTrack(null, track.getId(), "Battery Usage", - single(state -> new BatterySummaryPanel(state, track), true)); - return data; - } + @Override + public void set(int idx, Row row) { + super.set(idx, row); + capacity[idx] = row.getLong(FIRST_DATA_COLUMN + 0); + charge[idx] = row.getLong(FIRST_DATA_COLUMN + 1); + current[idx] = row.getLong(FIRST_DATA_COLUMN + 2); + } - private static CounterInfo onlyOne(ImmutableList counters) { - return (counters.size() != 1) ? null : counters.get(0); + @Override + public void copyRow(long time, int src, int dst) { + super.copyRow(time, src, dst); + capacity[dst] = capacity[src]; + charge[dst] = charge[src]; + current[dst] = current[src]; + } } - public static class Data extends Track.Data { - public final long[] ts; + public static class Values extends CombinedCountersTrack.Values { public final long[] capacity; public final long[] charge; public final long[] current; - public Data(DataRequest request, long[] ts, long[] capacity, long[] charge, long[] current) { - super(request); - this.ts = ts; - this.capacity = capacity; - this.charge = charge; - this.current = current; + public Values(int numRows, BinaryOperator combiner) { + super(numRows, combiner); + this.capacity = new long[numRows]; + this.charge = new long[numRows]; + this.current = new long[numRows]; + } + + @Override + public void set(int idx, Row row) { + super.set(idx, row); + capacity[idx] = row.getLong(FIRST_DATA_COLUMN + 0); + charge[idx] = row.getLong(FIRST_DATA_COLUMN + 1); + current[idx] = row.getLong(FIRST_DATA_COLUMN + 2); + } + + @Override + public void copyFrom(Values other, int src, int dst) { + capacity[dst] = other.capacity[src]; + charge[dst] = other.charge[src]; + current[dst] = other.current[src]; + } + + @Override + public String getTitle() { + return "Battery Usage"; } - public static Data empty(DataRequest req) { - return new Data(req, new long[0], new long[0], new long[0], new long[0]); + @Override + public Composite buildUi(Composite parent, State state) { + return new BatterySelectionView(parent, state, this); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CombinedCountersTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CombinedCountersTrack.java new file mode 100644 index 0000000000..f543f616c5 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/CombinedCountersTrack.java @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.models; + +import static com.google.gapid.perfetto.models.QueryEngine.createSpan; +import static com.google.gapid.perfetto.models.QueryEngine.createView; +import static com.google.gapid.perfetto.models.QueryEngine.createWindow; +import static com.google.gapid.perfetto.models.QueryEngine.dropTable; +import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.perfetto.models.QueryEngine.expectOneRow; +import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.MoreFutures.transformAsync; +import static java.lang.String.format; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static java.util.stream.IntStream.range; + +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.perfetto.TimeSpan; + +import java.util.Set; +import java.util.function.BinaryOperator; +import java.util.function.Consumer; + +/** + * {@link Track} that combines multiple counters into a single graph. + */ +public abstract class CombinedCountersTrack + > + extends Track.WithQueryEngine { + private final boolean needQuantize; + private final String viewSql; + private final String summarySql; + private final String dataSql; + private final String valueSql; + private final String rangeSql; + + public CombinedCountersTrack( + QueryEngine qe, String trackId, Column[] columns, boolean needQuantize) { + super(qe, trackId); + if (stream(columns).allMatch(Column::isNil)) { + throw new IllegalArgumentException("At least one counter needs to be non-nil"); + } + + this.needQuantize = needQuantize; + this.viewSql = buildViewSql(columns); + this.summarySql = buildSummarySql(columns, tableName("span")); + this.dataSql = buildDataSql(columns, tableName("span")); + this.valueSql = buildValueSql(columns, tableName("vals")); + this.rangeSql = buildRangeSql(columns, tableName("vals")); + } + + private static String buildViewSql(Column[] columns) { + // select id, ts, lead(ts, 1, ) over win - ts dur, [last_non_null(v_<#>) over win v_<#>]+ + // from ( + // select min(id) id, ts, [max(case when track_id = then value else null end) v_<#>]+ + // from counter where track_id in ([]+) + // group by ts) + // window win as (order by ts) + + StringBuilder sb = new StringBuilder() + .append("select id, ts, ") + .append("lead(ts, 1, (select end_ts from trace_bounds)) over win - ts dur"); + for (int i = 0; i < columns.length; i++) { + if (columns[i].isNil()) { + sb.append(", ").append(columns[i].sqlDefault).append(" v_").append(i); + } else { + sb.append(", last_non_null(v_").append(i).append(") over win v_").append(i); + } + } + sb.append(" from (select min(id) id, ts"); + for (int i = 0; i < columns.length; i++) { + Column c = columns[i]; + if (!c.isNil()) { + sb.append(", max(case when track_id = ").append(c.trackId).append(" then ") + .append("cast(value as ").append(c.sqlType).append(") else null end) v_").append(i); + } + } + sb.append(" from counter where track_id in "); + sb.append(stream(columns) + .filter(c -> !c.isNil()) + .map(c -> Long.toString(c.trackId)) + .collect(joining(", ", "(", ")"))); + sb.append(" group by ts) window win as (order by ts)"); + return sb.toString(); + } + + private static String buildSummarySql(Column[] columns, String tableName) { + // select id, min(start), max(end), [v_<#>]+ + // from ( + // select min(id) id, min(ts) start, max(ts + dur) end, [v_<#>]+ + // from + // group by quantum_ts) + // group by id + + StringBuilder sb = new StringBuilder(); + sb.append("select id, min(start), max(end)"); + for (int i = 0; i < columns.length; i++) { + Column c = columns[i]; + sb.append(", cast(") + .append(c.sqlAggregate).append("(") + .append("v_").append(i).append(") ") + .append("as ").append(c.sqlType).append(")"); + } + sb.append(" from ("); + sb.append("select min(id) id, min(ts) start, max(ts + dur) end"); + for (int i = 0; i < columns.length; i++) { + Column c = columns[i]; + sb.append(", cast(") + .append(c.sqlAggregate).append("(") + .append("v_").append(i).append(") ") + .append("as ").append(c.sqlType).append(") v_").append(i); + } + sb.append(" from ").append(tableName).append(" group by quantum_ts) group by id"); + return sb.toString(); + } + + private static String buildDataSql(Column[] columns, String span) { + // select id, ts, ts + dur, [coalesce(v_<#>, )]+ from + + StringBuilder sb = new StringBuilder() + .append("select id, ts, ts + dur"); + for (int i = 0; i < columns.length; i++) { + sb.append(", coalesce(v_").append(i).append(", ").append(columns[i].sqlDefault).append(")"); + } + sb.append(" from ").append(span); + return sb.toString(); + } + + private static String buildValueSql(Column[] columns, String tableName) { + // select id, ts, dur, [v_<#>]+ from where id = ? + + StringBuilder sb = new StringBuilder("select id, ts, dur"); + for (int i = 0; i < columns.length; i++) { + sb.append(", v_").append(i); + } + sb.append(" from ").append(tableName).append(" where id = %d"); + return sb.toString(); + } + + private static String buildRangeSql(Column[] columns, String tableName) { + // select id, ts, dur, [v_<#>]+ from
where ts + dur >= ? and ts <= ? order by ts + + StringBuilder sb = new StringBuilder("select id, ts, dur"); + for (int i = 0; i < columns.length; i++) { + sb.append(", v_").append(i); + } + sb.append(" from ").append(tableName) + .append(" where ts + dur >= %d and ts <= %d order by ts"); + return sb.toString(); + } + + @Override + protected ListenableFuture initialize() { + String vals = tableName("vals"); + String span = tableName("span"); + String window = tableName("window"); + return qe.queries( + dropTable(span), + dropTable(window), + dropView(vals), + createView(vals, viewSql), + createWindow(window), + createSpan(span, vals + ", " + window)); + } + + @Override + protected ListenableFuture computeData(DataRequest req) { + Window win = needQuantize ? Window.compute(req, 5) : Window.compute(req); + return transformAsync(win.update(qe, tableName("window")), $ -> computeData(req, win)); + } + + private ListenableFuture computeData(DataRequest req, Window win) { + return transform(qe.query(win.quantized ? summarySql : dataSql), res -> { + int rows = res.getNumRows(); + if (rows == 0) { + return createData(req, 0); + } + + D data = createData(req, rows + 1); + res.forEachRow(data::set); + data.copyRow(res.getLong(rows - 1, 2, 0), rows - 1, rows); + return data; + }); + } + + protected abstract D createData(DataRequest request, int numRows); + + public ListenableFuture getValue(long id) { + return transform(expectOneRow(qe.query(valueSql(id))), row -> { + V v = createValues(1, this::combine); + v.set(0, row); + return v; + }); + } + + private String valueSql(long id) { + return format(valueSql, id); + } + + public ListenableFuture getValues(TimeSpan ts) { + return transform(qe.query(rangeSql(ts)), res -> { + V v = createValues(res.getNumRows(), this::combine); + res.forEachRow(v::set); + return v; + }); + } + + private String rangeSql(TimeSpan ts) { + return format(rangeSql, ts.start, ts.end); + } + + protected abstract V createValues(int numRows, BinaryOperator combiner); + + private V combine(V a, V b) { + if (a.ts.length == 0) { + return b; + } else if (b.ts.length == 0) { + return a; + } + + long[] ts = new long[a.ts.length + b.ts.length]; + int count = combineTs(a.ts, b.ts, ts); + V v = createValues(count, this::combine); + System.arraycopy(ts, 0, v.ts, 0, count); + + int ai = 0, bi = 0, ri = 0; + for (; ai < a.ts.length && bi < b.ts.length; ri++) { + long ats = a.ts[ai], bts = b.ts[bi], rts = v.ts[ri]; + if (rts == ats) { + v.copyFrom(a, ai, ri); + ai++; + if (rts == bts) { + bi++; + } + } else { + v.copyFrom(b, bi, ri); + bi++; + } + } + + for (; ai < a.ts.length; ai++, ri++) { + v.copyFrom(a, ai, ri); + } + for (; bi < b.ts.length; bi++, ri++) { + v.copyFrom(b, bi, ri); + } + + for (int i = 1; i < count; i++) { + v.dur[i - 1] = v.ts[i] - v.ts[i - 1]; + } + v.dur[count - 1] = Math.max( + a.ts[a.ts.length - 1] + a.dur[a.dur.length - 1], + b.ts[b.ts.length - 1] + b.dur[b.dur.length - 1]) - v.ts[count - 1]; + + v.combineIds(a); + v.combineIds(b); + return v; + } + + private static int combineTs(long[] a, long[] b, long[] r) { + int ai = 0, bi = 0, ri = 0; + for (; ai < a.length && bi < b.length; ri++) { + long av = a[ai], bv = b[bi]; + if (av == bv) { + r[ri] = av; + ai++; + bi++; + } else if (av < bv) { + r[ri] = av; + ai++; + } else { + r[ri] = bv; + bi++; + } + } + // One of these copies does nothing. + System.arraycopy(a, ai, r, ri, a.length - ai); + System.arraycopy(b, bi, r, ri, b.length - bi); + return ri + (a.length - ai) + (b.length - bi); + } + + + public static class Data extends Track.Data { + protected static final int FIRST_DATA_COLUMN = 3; + + public final long[] id; + public final long[] ts; + + public Data(DataRequest request, int numRows) { + super(request); + this.id = new long[numRows]; + this.ts = new long[numRows]; + } + + public void set(int idx, QueryEngine.Row row) { + id[idx] = row.getLong(0); + ts[idx] = row.getLong(1); + } + + public void copyRow(long time, int src, int dst) { + id[dst] = id[src]; + ts[dst] = time; + } + } + + public abstract static class Values> implements Selection { + protected static final int FIRST_DATA_COLUMN = 3; + + public final long[] ts; + public final long[] dur; + private final Set valueKeys; + private final BinaryOperator combiner; + + public Values(int numRows, BinaryOperator combiner) { + this.ts = new long[numRows]; + this.dur = new long[numRows]; + this.valueKeys = Sets.newHashSet(); + this.combiner = combiner; + } + + public void combineIds(Values other) { + valueKeys.addAll(other.valueKeys); + } + + public void set(int idx, QueryEngine.Row row) { + valueKeys.add(row.getLong(0)); + ts[idx] = row.getLong(1); + dur[idx] = row.getLong(2); + } + + public abstract void copyFrom(T other, int src, int dst); + + @Override + public boolean contains(Long key) { + return valueKeys.contains(key); + } + + @Override + public void getRange(Consumer span) { + long min = stream(ts).min().getAsLong(); + long max = range(0, ts.length).mapToLong(i -> ts[i] + dur[i]).max().getAsLong(); + span.accept(new TimeSpan(min, max)); + } + + @Override + @SuppressWarnings("unchecked") + public T combine(T that) { + return combiner.apply((T)this, that); + } + } + + protected static class Column { + public final long trackId; + public final String sqlType; + public final String sqlDefault; + public final String sqlAggregate; + + public Column(long trackId, String sqlType, String sqlDefault, String sqlAggregate) { + this.trackId = trackId; + this.sqlType = sqlType; + this.sqlDefault = sqlDefault; + this.sqlAggregate = sqlAggregate; + } + + public static Column nil(String sqlDefault) { + return new Column(-1, "", sqlDefault, "max"); + } + + public boolean isNil() { + return trackId < 0; + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java b/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java index bc698d57b5..1f10262467 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/CounterInfo.java @@ -17,33 +17,57 @@ import static com.google.common.collect.Streams.stream; import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.MoreFutures.transformAsync; import static java.util.logging.Level.WARNING; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableListMultimap; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Perfetto; import com.google.gapid.perfetto.Unit; +import com.google.gapid.perfetto.models.QueryEngine.Row; import com.google.gapid.proto.device.GpuProfiling; import com.google.gapid.util.Range; +import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.logging.Logger; public class CounterInfo { private static final Logger LOG = Logger.getLogger(CounterInfo.class.getName()); + private static final String STATS_SQL = + "select c1.track_id, c1.count, c1.min, c1.max, c1.avg, c2.min, c2.max, c2.avg " + + "from (" + + // Regular stats query. + "select track_id, count(value) count, min(value) min, max(value) max, avg(value) avg " + + "from counter " + + "where (value + 1 > value or value - 1 < value) " + + "group by track_id) c1 " + + "left join (" + + // Monotonically increasing counter stats query. + "select track_id, min(value) min, max(value) max, avg(value) avg from (" + + "select track_id, lead(value) over win - value value " + + "from counter " + + "window win as (partition by track_id order by ts)" + + ") where (value + 1 > value or value - 1 < value) " + + "group by track_id" + + ") c2 on (c1.track_id = c2.track_id)"; + private static final String LIST_SQL = "select ct.id, ct.type, coalesce(cpu, gpu_id, upid, utid), ct.name, ct.description, " + - " ct.unit, count(value), min(value), max(value), avg(value) " + + " ct.unit " + "from counter_track ct " + "left join cpu_counter_track using (id) " + "left join gpu_counter_track using (id) " + "left join process_counter_track using (id) " + - "left join thread_counter_track using (id) " + - "left join counter on (track_id = ct.id) " + - "where (value + 1 > value or value - 1 < value) " + - "group by ct.id"; + "left join thread_counter_track using (id)"; + + private static final String LIST_COUNTER_GROUP_SQL = + "select group_id, track_id from gpu_counter_group"; private static final ImmutableMap UNITS = ImmutableMap. builder() .put(GpuProfiling.GpuCounterDescriptor.MeasureUnit.NONE_VALUE, Unit.NONE) @@ -103,7 +127,7 @@ public class CounterInfo { public final Range range; public CounterInfo(long id, Type type, long ref, String name, String description, Unit unit, - Interpolation interpolation, long count, double min, double max, double avg) { + Interpolation interpolation, long count, double min, double max, double avg, Range range) { this.id = id; this.type = type; this.ref = ref; @@ -115,13 +139,20 @@ public CounterInfo(long id, Type type, long ref, String name, String description this.min = min; this.max = max; this.avg = avg; - this.range = computeRange(unit, min, max); + this.range = range; + } + + public CounterInfo(long id, Type type, long ref, String name, String description, Unit unit, + Interpolation interpolation, long count, double min, double max, double avg) { + this(id, type, ref, name, description, unit, + interpolation, count, min, max, avg, computeRange(unit, min, max)); } - private CounterInfo(QueryEngine.Row row) { + private CounterInfo(QueryEngine.Row row, Interpolation interpolation, + long count, double min, double max, double avg) { this(row.getLong(0), Type.of(row.getString(1)), row.getLong(2), row.getString(3), - row.getString(4), unitFromString(row.getString(5)), Interpolation.of(row), row.getLong(6), - row.getDouble(7), row.getDouble(8), row.getDouble(9)); + row.getString(4), unitFromString(row.getString(5)), interpolation, + count, min, max, avg); } private static Range computeRange(Unit unit, double min, double max) { @@ -169,15 +200,63 @@ private static Unit[] parseUnits(String units) throws NumberFormatException { } public static ListenableFuture listCounters(Perfetto.Data.Builder data) { - return transform(data.qe.query(LIST_SQL), res -> { - ImmutableMap.Builder counters = ImmutableMap.builder(); - res.forEachRow((i, r) -> counters.put(r.getLong(0), new CounterInfo(r))); - return data.setCounters(counters.build()); + return transformAsync(listCounterGroups(data), $1 -> + transformAsync(computeStats(data), stats -> + transform(data.qe.query(LIST_SQL), res -> { + ImmutableMap.Builder counters = ImmutableMap.builder(); + List gpufreqCounters = Lists.newArrayList(); + res.forEachRow((i, r) -> { + CounterInfo newCounter = Interpolation.of(r) + .newCounter(r, stats.getOrDefault(r.getLong(0), Stats.ZERO)); + // Save references to "gpufreq" counters for range adjustment + if ("gpufreq".equals(newCounter.name)) { + gpufreqCounters.add(newCounter); + } else { + counters.put(newCounter.id, newCounter); + } + }); + + // Use maximum range for gpufreq counters to visualize them with correct proportion to each other. + if (gpufreqCounters.size() > 0) { + double gpufreqMax = 0; + for (CounterInfo c : gpufreqCounters) { + gpufreqMax = Math.max(gpufreqMax, c.max); + } + for (CounterInfo c : gpufreqCounters) { + CounterInfo newCounter = new CounterInfo(c.id, c.type, c.ref, c.name, + c.description, c.unit, c.interpolation, c.count, c.min, c.max, c.avg, + computeRange(c.unit, c.min, gpufreqMax)); + counters.put(newCounter.id, newCounter); + } + } + + return data.setCounters(counters.build()); + }))); + } + + private static ListenableFuture listCounterGroups(Perfetto.Data.Builder data) { + return transform(data.qe.query(LIST_COUNTER_GROUP_SQL), res -> { + ImmutableListMultimap.Builder groups = ImmutableListMultimap.builder(); + res.forEachRow((i, r) -> groups.put(r.getLong(0), r.getLong(1))); + return data.setCounterGroups(groups.build()); }); } + private static ListenableFuture> computeStats(Perfetto.Data.Builder data) { + return transform(data.qe.query(STATS_SQL), res -> res.map(row -> row.getLong(0), Stats::new)); + } + + public static boolean needQuantize(CounterInfo... infos) { + for (CounterInfo info : infos) { + if (info != null && info.count > Track.QUANTIZE_CUT_OFF) { + return true; + } + } + return false; + } + public static enum Type { - Global, Cpu, Gpu, Process, Thread; + Global, Cpu, Gpu, Process, Thread, Energy, Uid; public static Type of(String string) { switch (string) { @@ -185,6 +264,8 @@ public static Type of(String string) { case "gpu_counter_track": return Gpu; case "process_counter_track": return Process; case "thread_counter_track": return Thread; + case "energy_counter_track": return Energy; + case "uid_counter_track": return Uid; default: return Global; // Treat unknowns as global counters. } @@ -192,14 +273,69 @@ public static Type of(String string) { } public static enum Interpolation { - Delta, // the value represents the counter "amount" since the last sample. - Event; // the value represents the current value until the next sample. + // the value represents the counter "amount" since the last sample. + Delta { + @Override + public CounterInfo newCounter(QueryEngine.Row row, Stats stats) { + return new CounterInfo(row, this, stats.count, stats.stdMin, stats.stdMax, stats.stdAvg); + } + }, + // the value represents the current value until the next sample. + Event { + @Override + public CounterInfo newCounter(QueryEngine.Row row, Stats stats) { + return new CounterInfo(row, this, stats.count, stats.stdMin, stats.stdMax, stats.stdAvg); + } + }, + // the value monotonically increases and represents some total since boot/other time. + Monotonic { + @Override + public CounterInfo newCounter(QueryEngine.Row row, Stats stats) { + return new CounterInfo(row, this, stats.count, stats.monMin, stats.monMax, stats.monAvg); + } + }; public static Interpolation of(QueryEngine.Row row) { // Only GPU counters, and not the gpufreq counter, are Delta counters. + // Only Power Rail Tracks are Monotonic counters. // TODO: this should be part of the counter definition in the backend. - return (!"gpu_counter_track".equals(row.getString(1)) || - "gpufreq".equals(row.getString(3))) ? Event : Delta; + if ("gpu_counter_track".equals(row.getString(1)) && (!"gpufreq".equals(row.getString(3)))) { + return Delta; + } else if ((row.getString(3).startsWith("power.rails")) || ("energy_counter_track".equals(row.getString(1)))) { + return Monotonic; + } else { + return Event; + } + } + + public abstract CounterInfo newCounter(QueryEngine.Row row, Stats stats); + } + + private static class Stats { + public static final Stats ZERO = new Stats(0, 0, 0, 0, 0, 0, 0); + + public final long count; + public final double stdMin; //Standard Minimum Value for Counters + public final double stdMax; //Standard Maximum Value for Counters + public final double stdAvg; //Standard Average Value for Counters + public final double monMin; //Minimum value for Monotonic Counters in STATS_SQL + public final double monMax; //Maximum value for Monotonic Counters in STATS_SQL + public final double monAvg; //Average value for Monotonic Counters in STATS_SQL + + private Stats(long count, double stdMin, double stdMax, double stdAvg, double monMin, + double monMax, double monAvg) { + this.count = count; + this.stdMin = stdMin; + this.stdMax = stdMax; + this.stdAvg = stdAvg; + this.monMin = monMin; + this.monMax = monMax; + this.monAvg = monAvg; + } + + public Stats(QueryEngine.Row row) { + this(row.getLong(1), row.getDouble(2), row.getDouble(3), row.getDouble(4), row.getDouble(5), + row.getDouble(6), row.getDouble(7)); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java index 5987bf9472..7870b9b06b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/CounterTrack.java @@ -21,10 +21,12 @@ import static com.google.gapid.perfetto.models.QueryEngine.dropTable; import static com.google.gapid.perfetto.models.QueryEngine.dropView; import static com.google.gapid.perfetto.models.QueryEngine.expectOneRow; +import static com.google.gapid.util.Arrays.filled; import static com.google.gapid.util.MoreFutures.transform; import static com.google.gapid.util.MoreFutures.transformAsync; import static java.lang.String.format; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; @@ -34,23 +36,34 @@ import org.eclipse.swt.widgets.Composite; import java.util.Arrays; +import java.util.Map; import java.util.Set; import java.util.function.Consumer; public class CounterTrack extends Track.WithQueryEngine { private static final String VIEW_SQL_DELTA = - "select ts + 1 ts, lead(ts) over win - ts dur, lead(value) over win value " + + "select ts + 1 ts, lead(ts) over win - ts dur, lead(value) over win value, lead(id) over win id " + "from counter where track_id = %d window win as (order by ts)"; private static final String VIEW_SQL_EVENT = - "select ts, lead(ts, 1, (select end_ts from trace_bounds)) over win - ts dur, value " + + "select ts, lead(ts, 1, (select end_ts from trace_bounds)) over win - ts dur, value, id " + + "from counter where track_id = %d window win as (order by ts)"; + private static final String VIEW_SQL_MONOTONIC = + "select ts + 1 ts, lead(ts) over win - ts dur, lead(value) over win - value value, lead(id) over win id " + "from counter where track_id = %d window win as (order by ts)"; private static final String SUMMARY_SQL = - "select min(ts), max(ts + dur), avg(value) from %s group by quantum_ts"; - private static final String COUNTER_SQL = "select ts, ts + dur, value from %s"; - private static final String VALUE_SQL = "select ts, ts + dur, value from %s where ts = %d"; + "select min(ts), max(ts + dur), avg(value), best_id from " + + "(select *, first_value(id) over (partition by quantum_ts order by dur desc) as best_id from %s) " + + "group by quantum_ts"; + private static final String COUNTER_SQL = "select ts, ts + dur, value, id from %s"; + private static final String VALUE_SQL = "select ts, ts + dur, value, id from %s where id = %d"; private static final String RANGE_SQL = - "select ts, ts + dur, value from %s " + + "select ts, ts + dur, value, id from %s " + "where ts + dur >= %d and ts <= %d order by ts"; + private static final String STATS_SQL = + "select min(value), max(value), sum(value * dur) / sum(dur) avg from " + + "(select min(dur, ts + dur - %d, %d + 1 - ts) dur, value from " + + "%s where ts + dur >= %d and ts <= %d)"; + private final CounterInfo counter; @@ -81,6 +94,7 @@ private String viewSql() { switch (counter.interpolation) { case Delta: return format(VIEW_SQL_DELTA, counter.id); case Event: return format(VIEW_SQL_EVENT, counter.id); + case Monotonic: return format(VIEW_SQL_MONOTONIC, counter.id); default: throw new AssertionError(); } } @@ -99,13 +113,15 @@ private ListenableFuture computeData(DataRequest req, Window win) { return Data.empty(req); } - Data data = new Data(req, new long[rows + 1], new double[rows + 1]); + Data data = new Data(req, new long[rows + 1], new double[rows + 1], new long[rows + 1]); res.forEachRow((i, r) -> { data.ts[i] = r.getLong(0); data.values[i] = r.getDouble(2); + data.ids[i] = r.getLong(3); }); data.ts[rows] = res.getLong(rows - 1, 1, 0); data.values[rows] = data.values[rows - 1]; + data.ids[rows] = data.ids[rows - 1]; return data; }); } @@ -118,13 +134,15 @@ private String counterSQL() { return format(COUNTER_SQL, tableName("span")); } - public ListenableFuture getValue(long t) { - return transform(expectOneRow(qe.query(valueSql(t))), row -> { - Data data = new Data(null, new long[2], new double[2]); + public ListenableFuture getValue(long id) { + return transform(expectOneRow(qe.query(valueSql(id))), row -> { + Data data = new Data(null, new long[2], new double[2], new long[2]); data.ts[0] = row.getLong(0); data.ts[1] = row.getLong(1); data.values[0] = row.getDouble(2); data.values[1] = data.values[0]; + data.ids[0] = row.getLong(3); + data.ids[1] = data.ids[0]; return data; }); } @@ -136,66 +154,98 @@ public ListenableFuture getValues(TimeSpan ts) { return Data.empty(null); } - Data data = new Data(null, new long[rows + 1], new double[rows + 1]); + Data data = new Data(null, new long[rows + 1], new double[rows + 1], new long[rows + 1]); res.forEachRow((i, r) -> { data.ts[i] = r.getLong(0); data.values[i] = r.getDouble(2); + data.ids[i] = r.getLong(3); }); data.ts[rows] = res.getLong(rows - 1, 1, 0); data.values[rows] = data.values[rows - 1]; + data.ids[rows] = data.ids[rows - 1]; return data; }); } - private String valueSql(long t) { - return format(VALUE_SQL, tableName("vals"), t); + private String valueSql(long id) { + return format(VALUE_SQL, tableName("vals"), id); } private String rangeSql(TimeSpan ts) { return format(RANGE_SQL, tableName("vals"), ts.start, ts.end); } + public ListenableFuture getStats(TimeSpan span) { + return transform(expectOneRow(qe.query(statsSql(span))), + row -> new Stats(span, row.getDouble(0), row.getDouble(1), row.getDouble(2))); + } + + private String statsSql(TimeSpan span) { + return format(STATS_SQL, span.start, span.end, tableName("vals"), span.start, span.end); + } + public static class Data extends Track.Data { public final long[] ts; public final double[] values; + public final long[] ids; - public Data(DataRequest request, long[] ts, double[] values) { + public Data(DataRequest request, long[] ts, double[] values, long[] ids) { super(request); this.ts = ts; this.values = values; + this.ids = ids; } public static Data empty(DataRequest req) { - return new Data(req, new long[0], new double[0]); + return new Data(req, new long[0], new double[0], new long[0]); } } - public static class Values implements Selection, Selection.Builder { + public static class Stats { + public final TimeSpan span; + public final double min; + public final double max; + public final double avg; + + public Stats(TimeSpan span, double min, double max, double avg) { + this.span = span; + this.min = min; + this.max = max; + this.avg = avg; + } + + public Stats(CounterInfo counter) { + this(TimeSpan.ZERO, counter.min, counter.max, counter.avg); + } + + public boolean isTotal() { + return span.isEmpty(); + } + } + + public static class Values implements Selection { public final long[] ts; - public final String[] names; - public final double[][] values; - private final Set valueKeys = Sets.newHashSet(); + public final Map values = Maps.newHashMap(); // counter_name -> values. + public final Map ids = Maps.newHashMap(); // counter_name -> ids. + private final Set valueKeys = Sets.newHashSet(); public Values(String name, Data data) { this.ts = data.ts; - this.names = new String[] { name }; - this.values = new double[][] { data.values }; + this.values.put(name, data.values); + this.ids.put(name, data.ids); initKeys(); } - private Values(long[] ts, String[] names, double[][] values) { + private Values(long[] ts, Map values, Map ids) { this.ts = ts; - this.names = names; - this.values = values; + this.values.putAll(values); + this.ids.putAll(ids); initKeys(); } private void initKeys() { - for (String name : names) { - // Skip the last dummy entry for the range end. - Arrays.stream(ts, 0, ts.length - 1) - .boxed() - .forEach(t -> valueKeys.add(new Values.Key(name, t))); + for (long[] keys : ids.values()) { + Arrays.stream(keys).forEach(valueKeys::add); } } @@ -205,7 +255,7 @@ public String getTitle() { } @Override - public boolean contains(Values.Key key) { + public boolean contains(Long key) { return valueKeys.contains(key); } @@ -214,11 +264,6 @@ public Composite buildUi(Composite parent, State state) { return new CountersSelectionView(parent, state, this); } - @Override - public Selection.Builder getBuilder() { - return this; - } - @Override public void getRange(Consumer span) { if (ts.length >= 2) { @@ -236,35 +281,37 @@ public Values combine(Values other) { long[] newTs = combineTs(ts, other.ts); - double[][] newValues = new double[names.length + other.names.length][newTs.length]; + Map newValues = Maps.newHashMap(); + Map newIds = Maps.newHashMap(); + for (String name : this.values.keySet()) { + newValues.put(name, new double[newTs.length]); + newIds.put(name, filled(new long[newTs.length], -1)); + } + for (String name : other.values.keySet()) { + newValues.put(name, new double[newTs.length]); + newIds.put(name, filled(new long[newTs.length], -1)); + } + for (int i = 0, me = 0, them = 0; i < newTs.length; i++) { long rTs = newTs[i], meTs = ts[me], themTs = other.ts[them]; if (rTs == meTs) { - for (int n = 0; n < names.length; n++) { - newValues[n][i] = values[n][me]; + for (String name : this.values.keySet()) { + newValues.get(name)[i] = values.get(name)[me]; + newIds.get(name)[i] = ids.get(name)[me]; } me = Math.min(me + 1, ts.length - 1); - } else if (i > 0) { - for (int n = 0; n < names.length; n++) { - newValues[n][i] = newValues[n][i - 1]; - } } if (rTs == themTs) { - for (int n = 0; n < other.names.length; n++) { - newValues[n + names.length][i] = other.values[n][them]; + for (String name : other.values.keySet()) { + newValues.get(name)[i] = other.values.get(name)[them]; + newIds.get(name)[i] = other.ids.get(name)[them]; } them = Math.min(them + 1, other.ts.length - 1); - } else if (i > 0) { - for (int n = 0; n < other.names.length; n++) { - newValues[names.length + n][i] = newValues[names.length + n][i - 1]; - } } } - String[] newNames = Arrays.copyOf(names, names.length + other.names.length); - System.arraycopy(other.names, 0, newNames, names.length, other.names.length); - return new Values(newTs, newNames, newValues); + return new Values(newTs, newValues, newIds); } private static long[] combineTs(long[] a, long[] b) { @@ -293,36 +340,5 @@ private static long[] combineTs(long[] a, long[] b) { r[newLength - 1] = Math.max(a[a.length - 1], b[b.length - 1]); return Arrays.copyOf(r, newLength); // Truncate array. } - - @Override - public Selection build() { - return this; - } - - public static class Key { - public final String name; - public final long ts; - - public Key(String name, long ts) { - this.name = name; - this.ts = ts; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (!(obj instanceof Key)) { - return false; - } - Key o = (Key)obj; - return name.equals(o.name) && ts == o.ts; - } - - @Override - public int hashCode() { - return name.hashCode() ^ Long.hashCode(ts); - } - } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CpuSummaryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CpuSummaryTrack.java index a94ce3ab88..3c9999e23b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/CpuSummaryTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/CpuSummaryTrack.java @@ -22,11 +22,9 @@ import static com.google.gapid.util.MoreFutures.transformAsync; import static java.lang.String.format; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; - -import java.util.List; +import com.google.gapid.perfetto.models.CpuTrack.Slices; /** * {@link Track} summarizing the total CPU usage. @@ -36,7 +34,7 @@ public class CpuSummaryTrack extends Track.WithQueryEngine "select quantum_ts, sum(dur)/cast(%d * %d as float) " + "from %s where utid != 0 group by quantum_ts"; private static final String SLICE_RANGE_SQL = - "select row_id, ts, dur, cpu, utid, upid, end_state, priority " + + "select sched.id, ts, dur, cpu, utid, upid, end_state, priority " + "from sched left join thread using(utid) " + "where utid != 0 and ts < %d and ts_end >= %d"; @@ -76,12 +74,8 @@ private String sql(long ns) { return format(DATA_SQL, numCpus, ns, tableName("span")); } - public ListenableFuture> getSlices(TimeSpan ts) { - return transform(qe.query(sliceRangeSql(ts)), result -> { - List slices = Lists.newArrayList(); - result.forEachRow((i, r) -> slices.add(new CpuTrack.Slice(r))); - return slices; - }); + public ListenableFuture getSlices(TimeSpan ts) { + return transform(qe.query(sliceRangeSql(ts)), Slices::new); } private static String sliceRangeSql(TimeSpan ts) { diff --git a/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java index 26f477a213..0992c5d332 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/CpuTrack.java @@ -25,20 +25,18 @@ import static java.lang.String.format; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.ThreadState; import com.google.gapid.perfetto.TimeSpan; -import com.google.gapid.perfetto.views.CpuSliceSelectionView; import com.google.gapid.perfetto.views.CpuSlicesSelectionView; import com.google.gapid.perfetto.views.State; import org.eclipse.swt.widgets.Composite; -import java.util.Collections; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; @@ -49,20 +47,24 @@ */ public class CpuTrack extends Track.WithQueryEngine { private static final String SUMMARY_SQL = - "select quantum_ts, sum(dur)/cast(%d as float) " + + "select quantum_ts, group_concat(id) ids, sum(dur)/cast(%d as float) util " + "from %s where cpu = %d and utid != 0 " + "group by quantum_ts"; private static final String SLICES_SQL = - "select ts, dur, utid, row_id from %s where cpu = %d and utid != 0"; + "select ts, dur, utid, id from %s where cpu = %d and utid != 0"; private static final String SLICE_SQL = - "select row_id, ts, dur, cpu, utid, upid, end_state, priority " + - "from sched left join thread using(utid) where row_id = %d"; + "select sched.id, ts, dur, cpu, utid, upid, end_state, priority " + + "from sched left join thread using(utid) where sched.id = %d"; private static final String SLICE_RANGE_SQL = - "select row_id, ts, dur, cpu, utid, upid, end_state, priority " + + "select sched.id, ts, dur, cpu, utid, upid, end_state, priority " + "from sched left join thread using(utid) " + "where cpu = %d and utid != 0 and ts < %d and ts_end >= %d"; + private static final String SLICE_RANGE_FOR_IDS_SQL = + "select sched.id, ts, dur, cpu, utid, upid, end_state, priority " + + "from sched left join thread using(utid) " + + "where cpu = %d and sched.id in (%s)"; private static final String SLICE_RANGE_FOR_THREAD_SQL = - "select row_id, ts, dur, cpu, utid, upid, end_state, priority " + + "select sched.id, ts, dur, cpu, utid, upid, end_state, priority " + "from sched left join thread using(utid) " + "where utid = %d and ts < %d and ts_end >= %d"; @@ -96,8 +98,14 @@ protected ListenableFuture computeData(DataRequest req) { private ListenableFuture computeSummary(DataRequest req, Window w) { return transform(qe.query(summarySql(w.bucketSize)), result -> { - Data data = new Data(req, w.bucketSize, new double[w.getNumberOfBuckets()]); - result.forEachRow(($, r) -> data.utilizations[r.getInt(0)] = r.getDouble(1)); + int len = w.getNumberOfBuckets(); + String[] concatedIds = new String[len]; + Arrays.fill(concatedIds, ""); + Data data = new Data(req, w.bucketSize, concatedIds, new double[len]); + result.forEachRow(($, r) -> { + data.concatedIds[r.getInt(0)] = r.getString(1); + data.utilizations[r.getInt(0)] = r.getDouble(2); + }); return data; }); } @@ -125,38 +133,38 @@ private String slicesSql() { return format(SLICES_SQL, tableName("span"), cpu.id); } - public ListenableFuture getSlice(long id) { + public ListenableFuture getSlice(long id) { return getSlice(qe, id); } - public static ListenableFuture getSlice(QueryEngine qe, long id) { - return transform(expectOneRow(qe.query(sliceSql(id))), Slice::new); + public static ListenableFuture getSlice(QueryEngine qe, long id) { + return transform(expectOneRow(qe.query(sliceSql(id))), Slices::new); } private static String sliceSql(long id) { return format(SLICE_SQL, id); } - public ListenableFuture> getSlices(TimeSpan ts) { - return transform(qe.query(sliceRangeSql(cpu.id, ts)), result -> { - List slices = Lists.newArrayList(); - result.forEachRow((i, r) -> slices.add(new Slice(r))); - return slices; - }); + public ListenableFuture getSlices(TimeSpan ts) { + return transform(qe.query(sliceRangeSql(cpu.id, ts)), Slices::new); } - public static ListenableFuture> getSlices(QueryEngine qe, long utid, TimeSpan ts) { - return transform(qe.query(sliceRangeForThreadSql(utid, ts)), result -> { - List slices = Lists.newArrayList(); - result.forEachRow((i, r) -> slices.add(new Slice(r))); - return slices; - }); + public ListenableFuture getSlices(String ids) { + return transform(qe.query(sliceRangeForIdsSql(cpu.id, ids)), Slices::new); + } + + public static ListenableFuture getSlices(QueryEngine qe, long utid, TimeSpan ts) { + return transform(qe.query(sliceRangeForThreadSql(utid, ts)), Slices::new); } private static String sliceRangeSql(int cpu, TimeSpan ts) { return format(SLICE_RANGE_SQL, cpu, ts.end, ts.start); } + private static String sliceRangeForIdsSql(int cpu, String ids) { + return format(SLICE_RANGE_FOR_IDS_SQL, cpu, ids); + } + private static String sliceRangeForThreadSql(long utid, TimeSpan ts) { return format(SLICE_RANGE_FOR_THREAD_SQL, utid, ts.end, ts.start); } @@ -165,6 +173,7 @@ public static class Data extends Track.Data { public final Kind kind; // Summary. public final long bucketSize; + public final String[] concatedIds; // Concated ids for all cpu slices in a each time bucket. public final double[] utilizations; // Slice. public final long[] ids; @@ -172,10 +181,11 @@ public static class Data extends Track.Data { public final long[] ends; public final long[] utids; - public Data(DataRequest request, long bucketSize, double[] utilizations) { + public Data(DataRequest request, long bucketSize, String[] concatedIds, double[] utilizations) { super(request); this.kind = Kind.summary; this.bucketSize = bucketSize; + this.concatedIds = concatedIds; this.utilizations = utilizations; this.ids = null; this.starts = null; @@ -191,6 +201,7 @@ public Data(DataRequest request, long[] ids, long[] starts, long[] ends, long[] this.ends = ends; this.utids = utids; this.bucketSize = 0; + this.concatedIds = null; this.utilizations = null; } @@ -199,77 +210,37 @@ public static enum Kind { } } - public static class Slice implements Selection { - public final long id; - public final long time; - public final long dur; - public final int cpu; - public final long utid; - public final long upid; - public final ThreadState endState; - public final int priority; - - public Slice( - long id, long time, long dur, int cpu, long utid, long upid, ThreadState endState, int priority) { - this.id = id; - this.time = time; - this.dur = dur; - this.cpu = cpu; - this.utid = utid; - this.upid = upid; - this.endState = endState; - this.priority = priority; + public static class Slices implements Selection { + private int count = 0; + public final List ids = Lists.newArrayList(); + public final List times = Lists.newArrayList(); + public final List durs = Lists.newArrayList(); + public final List cpus = Lists.newArrayList(); + public final List utids = Lists.newArrayList(); + public final List upids = Lists.newArrayList(); + public final List endStates = Lists.newArrayList(); + public final List priorities = Lists.newArrayList(); + public final Set sliceKeys = Sets.newHashSet(); + + public Slices(QueryEngine.Row row) { + this.add(row); } - public Slice(QueryEngine.Row row) { - this(row.getLong(0), row.getLong(1), row.getLong(2), - row.getInt(3), row.getLong(4), row.getLong(5), - ThreadState.of(row.getString(6)), row.getInt(7)); + public Slices(QueryEngine.Result result) { + result.forEachRow((i, row) -> this.add(row)); } - @Override - public String getTitle() { - return "CPU Slices"; - } - - @Override - public boolean contains(Long key) { - return id == key; - } - - @Override - public Composite buildUi(Composite parent, State state) { - return new CpuSliceSelectionView(parent, state, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(Lists.newArrayList(this)); - } - - @Override - public void getRange(Consumer span) { - if (dur > 0) { - span.accept(new TimeSpan(time, time + dur)); - } - } - - @Override - public String toString() { - return "Slice{@" + time + " +" + dur + " " + utid + " " + endState + "/" + priority + "}"; - } - } - - public static class Slices implements Selection { - private final List slices; - public final ImmutableList processes; - public final ImmutableSet sliceKeys; - - public Slices(List slices, ImmutableList processes, - ImmutableSet sliceKeys) { - this.slices = slices; - this.processes = processes; - this.sliceKeys = sliceKeys; + private void add(QueryEngine.Row row) { + this.count++; + this.ids.add(row.getLong(0)); + this.times.add(row.getLong(1)); + this.durs.add(row.getLong(2)); + this.cpus.add(row.getInt(3)); + this.utids.add(row.getLong(4)); + this.upids.add(row.getLong(5)); + this.endStates.add(ThreadState.of(row.getString(6))); + this.priorities.add(row.getInt(7)); + this.sliceKeys.add(row.getLong(0)); } @Override @@ -284,89 +255,84 @@ public boolean contains(Long key) { @Override public Composite buildUi(Composite parent, State state) { - return new CpuSlicesSelectionView(parent, state, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(slices); + if (count <= 0) { + return null; + } else { + return new CpuSlicesSelectionView(parent, state, this); + } } @Override public void getRange(Consumer span) { - for (Slice slice : slices) { - slice.getRange(span); - } - } - } - - public static class SlicesBuilder implements Selection.Builder { - private final List slices; - private final Map processes = Maps.newHashMap(); - private final Set sliceKeys = Sets.newHashSet(); - - public SlicesBuilder(List slices) { - this.slices = slices; - for (Slice slice : slices) { - processes.computeIfAbsent(slice.upid, ByProcess.Builder::new).add(slice); - sliceKeys.add(slice.id); + for (int i = 0; i < count; i++) { + if (durs.get(i) > 0) { + span.accept(new TimeSpan(times.get(i), times.get(i) + durs.get(i))); + } } } @Override - public SlicesBuilder combine(SlicesBuilder other) { - this.slices.addAll(other.slices); - for (Map.Entry e : other.processes.entrySet()) { - processes.merge(e.getKey(), e.getValue(), ByProcess.Builder::combine); + public Slices combine(Slices other) { + for (int i = 0; i < other.count; i++) { + if (!this.sliceKeys.contains(other.ids.get(i))) { + this.count++; + this.ids.add(other.ids.get(i)); + this.times.add(other.times.get(i)); + this.durs.add(other.durs.get(i)); + this.cpus.add(other.cpus.get(i)); + this.utids.add(other.utids.get(i)); + this.upids.add(other.upids.get(i)); + this.endStates.add(other.endStates.get(i)); + this.priorities.add(other.priorities.get(i)); + this.sliceKeys.add(other.ids.get(i)); + } } - sliceKeys.addAll(other.sliceKeys); return this; } - @Override - public Selection build() { - return new Slices(slices, processes.values().stream() - .map(ByProcess.Builder::build) - .sorted((p1, p2) -> Long.compare(p2.dur, p1.dur)) - .collect(toImmutableList()), ImmutableSet.copyOf(sliceKeys)); + public int getCount() { + return count; } } + public static ByProcess[] organizeSlicesByProcess(Slices slices) { + Map processes = Maps.newHashMap(); + for (int i = 0; i < slices.count; i++) { + processes.computeIfAbsent(slices.upids.get(i), upid -> new ByProcess.Builder(upid, slices)).add(i); + } + return processes.values().stream() + .map(ByProcess.Builder::build) + .sorted((p1, p2) -> Long.compare(p2.dur, p1.dur)) + .toArray(ByProcess[]::new); + } + public static class ByProcess { public final long pid; public final long dur; public final ImmutableList threads; - public ByProcess(long pid, long dur, ImmutableList threads) { + public ByProcess(long pid, ImmutableList threads) { this.pid = pid; - this.dur = dur; + this.dur = threads.stream().mapToLong(t -> t.dur).sum(); this.threads = threads; } public static class Builder { public final long pid; - public long dur = 0; + private final Slices slices; public final Map threads = Maps.newHashMap(); - public Builder(long pid) { + public Builder(long pid, Slices slices) { this.pid = pid; + this.slices = slices; } - public void add(Slice slice) { - dur += slice.dur; - threads.computeIfAbsent(slice.utid, ByThread.Builder::new).add(slice); - } - - public Builder combine(Builder other) { - dur += other.dur; - for (Map.Entry e : other.threads.entrySet()) { - threads.merge(e.getKey(), e.getValue(), ByThread.Builder::combine); - } - return this; + public void add(int index) { + threads.computeIfAbsent(slices.utids.get(index), utid -> new ByThread.Builder(utid, slices)).add(index); } public ByProcess build() { - return new ByProcess(pid, dur, threads.values().stream() + return new ByProcess(pid, threads.values().stream() .map(ByThread.Builder::build) .sorted((t1, t2) -> Long.compare(t2.dur, t1.dur)) .collect(toImmutableList())); @@ -377,39 +343,39 @@ public ByProcess build() { public static class ByThread { public final long tid; public final long dur; - public final ImmutableList slices; + public final Slices slices; + public final ImmutableList sliceIndexes; - public ByThread(long tid, long dur, ImmutableList slices) { + public ByThread(long tid, long dur, Slices slices, ImmutableList sliceIndexes) { this.tid = tid; this.dur = dur; this.slices = slices; + this.sliceIndexes = sliceIndexes; } public static class Builder { private final long tid; private long dur = 0; - private final List slices = Lists.newArrayList(); + private final Slices slices; + private final List sliceIndexes = Lists.newArrayList(); + private final Set sliceKeys = Sets.newHashSet(); - public Builder(long tid) { + public Builder(long tid, Slices slices) { this.tid = tid; + this.slices = slices; } - public void add(Slice slice) { - dur += slice.dur; - slices.add(slice); - } - - public Builder combine(Builder other) { - dur += other.dur; - slices.addAll(other.slices); - return this; + public void add(int index) { + if (!sliceKeys.contains(slices.ids.get(index))) { + dur += slices.durs.get(index); + sliceIndexes.add(index); + sliceKeys.add(slices.ids.get(index)); + } } public ByThread build() { - Collections.sort(slices, (s1, s2) -> Long.compare(s2.dur, s1.dur)); - return new ByThread(tid, dur, slices.stream() - .sorted((s1, s2) -> Long.compare(s2.dur, s1.dur)) - .collect(toImmutableList())); + sliceIndexes.sort((i1, i2) -> Long.compare(slices.durs.get(i2), slices.durs.get(i1))); + return new ByThread(tid, dur, slices, sliceIndexes.stream().collect(toImmutableList())); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/EnergyBreakdownTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/EnergyBreakdownTrack.java new file mode 100644 index 0000000000..291a3bcead --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/EnergyBreakdownTrack.java @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.models; + +import static com.google.gapid.perfetto.models.QueryEngine.createSpan; +import static com.google.gapid.perfetto.models.QueryEngine.createView; +import static com.google.gapid.perfetto.models.QueryEngine.createWindow; +import static com.google.gapid.perfetto.models.QueryEngine.dropTable; +import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.perfetto.models.QueryEngine.expectOneRow; +import static com.google.gapid.util.Arrays.filled; +import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.MoreFutures.transformAsync; +import static java.lang.String.format; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.views.EnergyBreakdownSelectionView; +import com.google.gapid.perfetto.views.State; +import java.util.Arrays; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import org.eclipse.swt.widgets.Composite; + +public class EnergyBreakdownTrack extends Track.WithQueryEngine { + private static final String VIEW_SQL_MONOTONIC = + "select ts + 1 ts, lead(ts) over win - ts dur, lead(value) over win - value value, lead(id)" + + " over win id from counter where track_id = %d window win as (order by ts)"; + private static final String SUMMARY_SQL = + "select min(ts), max(ts + dur), avg(value), best_id from (select *, first_value(id) over" + + " (partition by quantum_ts order by dur desc) as best_id from %s) group by quantum_ts"; + private static final String COUNTER_SQL = "select ts, ts + dur, value, id from %s"; + private static final String VALUE_SQL = "select ts, ts + dur, value, id from %s where id = %d"; + private static final String RANGE_SQL = + "select ts, ts + dur, value, id from %s " + "where ts + dur >= %d and ts <= %d order by ts"; + private static final String STATS_SQL = + "select min(value), max(value), sum(value * dur) / sum(dur) avg from " + + "(select min(dur, ts + dur - %d, %d + 1 - ts) dur, value from " + + "%s where ts + dur >= %d and ts <= %d)"; + private static final String UID_SQL = + "select uid from energy_per_uid_counter_track where consumer_id in (Select consumer_id from" + + " energy_counter_track where id = %d)"; + + private static final String UID_VALUES_SQL = + "Select value, uid, ts, dur from (Select c1.ts + 1 ts, lead(c1.ts) over win - c1.ts dur," + + " lead(c1.value) over win - c1.value value, lead(uid.uid) over win uid from counter c1" + + " inner join uid_counter_track as uid on uid.id = c1.track_id where uid.uid = %d window" + + " win as (order by ts)) where ts + dur >= %d and ts <= %d"; + + private final CounterInfo counter; + + public EnergyBreakdownTrack(QueryEngine qe, CounterInfo counter) { + super(qe, "energy_counter_" + counter.id); + this.counter = counter; + } + + public CounterInfo getCounter() { + return counter; + } + + @Override + protected ListenableFuture initialize() { + String vals = tableName("vals"); + String span = tableName("span"); + String window = tableName("window"); + return qe.queries( + dropTable(span), + dropTable(window), + dropView(vals), + createView(vals, viewSql()), + createWindow(window), + createSpan(span, vals + ", " + window)); + } + + private String viewSql() { + return format(VIEW_SQL_MONOTONIC, counter.id); + } + + @Override + protected ListenableFuture computeData(DataRequest req) { + Window win = + (counter.count > Track.QUANTIZE_CUT_OFF) ? Window.compute(req, 5) : Window.compute(req); + return transformAsync(win.update(qe, tableName("window")), $ -> computeData(req, win)); + } + + private ListenableFuture computeData(DataRequest req, Window win) { + return transform( + qe.query(win.quantized ? summarySql() : counterSQL()), + res -> { + int rows = res.getNumRows(); + if (rows == 0) { + return Data.empty(req); + } + + Data data = new Data(req, new long[rows + 1], new double[rows + 1], new long[rows + 1]); + res.forEachRow( + (i, r) -> { + data.ts[i] = r.getLong(0); + data.values[i] = r.getDouble(2); + data.ids[i] = r.getLong(3); + }); + data.ts[rows] = res.getLong(rows - 1, 1, 0); + data.values[rows] = data.values[rows - 1]; + data.ids[rows] = data.ids[rows - 1]; + return data; + }); + } + + private String summarySql() { + return format(SUMMARY_SQL, tableName("span")); + } + + private String counterSQL() { + return format(COUNTER_SQL, tableName("span")); + } + + // Updates the uidValues map with energy values corresponding to the uids received + private ListenableFuture computerUidEnergyValues( + Long start, Long end, Data counterData, long uid) { + return transform( + qe.query(uidValuesSQL(uid, start, end)), + res -> { + int rows = res.getNumRows(); + double[] values = new double[rows + 1]; + if (rows == 0) { + return counterData; + } + res.forEachRow( + (i, r) -> { + values[i] = r.getDouble(0); + }); + values[rows] = values[rows - 1]; + counterData.uidValues.put(uid, values); + return counterData; + }); + } + + // Adda uid energy values per uid in the map under Data + private ListenableFuture computeUids(Data counterData, long id) { + return transform( + qe.query(uidSQL(id)), + res -> { + int rows = res.getNumRows(); + + if (rows == 0) { + return counterData; + } + + counterData.hasUid = true; + int previousRows = counterData.ts.length; + long[] uids = new long[rows + 1]; + res.forEachRow( + (i, r) -> { + uids[i] = r.getLong(0); + }); + uids[rows] = uids[rows - 1]; + counterData.uids = uids; + return counterData; + }); + } + + public ListenableFuture getUidEnergyValue( + Long start, Long end, int index, long[] uids, ListenableFuture data) { + return transformAsync( + data, + res -> { + if (!res.hasUid || (index == uids.length - 1)) return data; + ListenableFuture x = computerUidEnergyValues(start, end, res, uids[index]); + return getUidEnergyValue(start, end, index + 1, uids, x); + }); + } + + // Gets energy value for selected energy consumer + public ListenableFuture getEnergyValue(long id, long counterId) { + return transformAsync( + expectOneRow(qe.query(valueSql(id))), + row -> { + Data data = new Data(null, new long[2], new double[2], new long[2]); + data.ts[0] = row.getLong(0); + data.ts[1] = row.getLong(1); + data.values[0] = row.getDouble(2); + data.values[1] = data.values[0]; + data.ids[0] = row.getLong(3); + data.ids[1] = data.ids[0]; + return computeUids(data, counterId); + }); + } + + // counterId -> uid value in uid_counter_table + public ListenableFuture getValue(long id, long counterId) { + ListenableFuture data = transform(getEnergyValue(id, counterId), res -> res); + return transformAsync( + data, + res -> { + if (res.ts.length == 0 || !res.hasUid) return data; + return getUidEnergyValue(res.ts[0], res.ts[1], 0, res.uids, data); + }); + } + + public ListenableFuture getValues(TimeSpan ts) { + return transform( + qe.query(rangeSql(ts)), + res -> { + int rows = res.getNumRows(); + if (rows == 0) { + return Data.empty(null); + } + + Data data = new Data(null, new long[rows + 1], new double[rows + 1], new long[rows + 1]); + res.forEachRow( + (i, r) -> { + data.ts[i] = r.getLong(0); + data.values[i] = r.getDouble(2); + data.ids[i] = r.getLong(3); + }); + data.ts[rows] = res.getLong(rows - 1, 1, 0); + data.values[rows] = data.values[rows - 1]; + data.ids[rows] = data.ids[rows - 1]; + return data; + }); + } + + private String valueSql(long id) { + return format(VALUE_SQL, tableName("vals"), id); + } + + private String uidValuesSQL(long id, long start, long end) { + return format(UID_VALUES_SQL, id, start, end); + } + + private String uidSQL(long id) { + return format(UID_SQL, id); + } + + private String rangeSql(TimeSpan ts) { + return format(RANGE_SQL, tableName("vals"), ts.start, ts.end); + } + + public ListenableFuture getStats(TimeSpan span) { + return transform( + expectOneRow(qe.query(statsSql(span))), + row -> new Stats(span, row.getDouble(0), row.getDouble(1), row.getDouble(2))); + } + + private String statsSql(TimeSpan span) { + return format(STATS_SQL, span.start, span.end, tableName("vals"), span.start, span.end); + } + + public static class Data extends Track.Data { + public final long[] ts; + public final double[] values; + public final long[] ids; + public long[] uids; + public boolean hasUid = false; // checks if the consumer has any uids available + public Map uidValues = Maps.newHashMap(); // uid -> uid energy values + + public Data(DataRequest request, long[] ts, double[] values, long[] ids) { + super(request); + this.ts = ts; + this.values = values; + this.ids = ids; + } + + public static Data empty(DataRequest req) { + return new Data(req, new long[0], new double[0], new long[0]); + } + } + + public static class Stats { + public final TimeSpan span; + public final double min; + public final double max; + public final double avg; + + public Stats(TimeSpan span, double min, double max, double avg) { + this.span = span; + this.min = min; + this.max = max; + this.avg = avg; + } + + public Stats(CounterInfo counter) { + this(TimeSpan.ZERO, counter.min, counter.max, counter.avg); + } + + public boolean isTotal() { + return span.isEmpty(); + } + } + + public static class Values implements Selection { + public final long[] ts; + public final Map values = Maps.newHashMap(); // counter_name -> values. + public final Map ids = Maps.newHashMap(); // counter_name -> ids. + private final Set valueKeys = Sets.newHashSet(); + public Map uidValues = Maps.newHashMap(); // uid-> uid counter values + + public Values(String name, Data data) { + this.ts = data.ts; + this.values.put(name, data.values); + this.ids.put(name, data.ids); + this.uidValues = data.uidValues; + initKeys(); + } + + private Values( + long[] ts, + Map values, + Map ids, + Map uidValues) { + this.ts = ts; + this.values.putAll(values); + this.ids.putAll(ids); + this.uidValues = uidValues; + initKeys(); + } + + private void initKeys() { + for (long[] keys : ids.values()) { + Arrays.stream(keys).forEach(valueKeys::add); + } + } + + @Override + public String getTitle() { + return "Energy Counters"; + } + + @Override + public boolean contains(Long key) { + return valueKeys.contains(key); + } + + @Override + public Composite buildUi(Composite parent, State state) { + return new EnergyBreakdownSelectionView(parent, state, this); + } + + @Override + public void getRange(Consumer span) { + if (ts.length >= 2) { + span.accept(new TimeSpan(ts[0], ts[ts.length - 1])); + } + } + + @Override + public Values combine(Values other) { + if (ts.length == 0) { + return other; + } else if (other.ts.length == 0) { + return this; + } + + long[] newTs = combineTs(ts, other.ts); + + Map newValues = Maps.newHashMap(); + Map newIds = Maps.newHashMap(); + Map newUidValues = Maps.newHashMap(); + + for (String name : this.values.keySet()) { + newValues.put(name, new double[newTs.length]); + newIds.put(name, filled(new long[newTs.length], -1)); + } + for (String name : other.values.keySet()) { + newValues.put(name, new double[newTs.length]); + newIds.put(name, filled(new long[newTs.length], -1)); + } + + for (Long uid : this.uidValues.keySet()) { + newUidValues.put(uid, new double[newTs.length]); + } + for (Long uid : other.uidValues.keySet()) { + newUidValues.put(uid, new double[newTs.length]); + } + + for (int i = 0, me = 0, them = 0; i < newTs.length; i++) { + long rTs = newTs[i], meTs = ts[me], themTs = other.ts[them]; + if (rTs == meTs) { + for (String name : this.values.keySet()) { + newValues.get(name)[i] = values.get(name)[me]; + newIds.get(name)[i] = ids.get(name)[me]; + } + for (Long uid : this.uidValues.keySet()) { + newUidValues.get(uid)[i] = uidValues.get(uid)[me]; + } + me = Math.min(me + 1, ts.length - 1); + } + + if (rTs == themTs) { + for (String name : other.values.keySet()) { + newValues.get(name)[i] = other.values.get(name)[them]; + newIds.get(name)[i] = other.ids.get(name)[them]; + } + + for (Long uid : other.uidValues.keySet()) { + newUidValues.get(uid)[i] = other.uidValues.get(uid)[me]; + } + them = Math.min(them + 1, other.ts.length - 1); + } + } + return new Values(newTs, newValues, newIds, newUidValues); + } + + private static long[] combineTs(long[] a, long[] b) { + // Remember, the last value in both a and b needs to be ignored. + long[] r = new long[a.length + b.length - 1]; + int ai = 0, bi = 0, ri = 0; + for (; ai < a.length - 1 && bi < b.length - 1; ri++) { + long av = a[ai], bv = b[bi]; + if (av == bv) { + r[ri] = av; + ai++; + bi++; + } else if (av < bv) { + r[ri] = av; + ai++; + } else { + r[ri] = bv; + bi++; + } + } + // One of these copies does nothing. + System.arraycopy(a, ai, r, ri, a.length - ai - 1); + System.arraycopy(b, bi, r, ri, b.length - bi - 1); + + int newLength = ri + a.length - ai + b.length - bi - 1; + r[newLength - 1] = Math.max(a[a.length - 1], b[b.length - 1]); + return Arrays.copyOf(r, newLength); // Truncate array. + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/FrameEventsTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/FrameEventsTrack.java index d732296ddf..8dc53b003c 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/FrameEventsTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/FrameEventsTrack.java @@ -16,24 +16,18 @@ package com.google.gapid.perfetto.models; -import static com.google.gapid.perfetto.models.QueryEngine.createSpan; -import static com.google.gapid.perfetto.models.QueryEngine.createView; import static com.google.gapid.perfetto.models.QueryEngine.createWindow; import static com.google.gapid.perfetto.models.QueryEngine.dropTable; -import static com.google.gapid.perfetto.models.QueryEngine.dropView; import static com.google.gapid.perfetto.models.QueryEngine.expectOneRow; import static com.google.gapid.util.MoreFutures.transform; import static com.google.gapid.util.MoreFutures.transformAsync; import static java.lang.String.format; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; -import com.google.gapid.perfetto.views.FrameEventsMultiSelectionView; import com.google.gapid.perfetto.views.FrameEventsSelectionView; import com.google.gapid.perfetto.views.State; @@ -50,193 +44,184 @@ // TODO: dedupe code with SliceTrack. public class FrameEventsTrack extends Track.WithQueryEngine{ private static final String BASE_COLUMNS = - "id, ts, dur, category, name, depth, stack_id, parent_stack_id, arg_set_id"; - private static final String SLICES_VIEW = - "select " + BASE_COLUMNS + " from gpu_slice where track_id = %d"; + "id, ts, dur, name, depth, stack_id, parent_stack_id, arg_set_id, frame_number"; + private static final String STAT_COLUMNS = + "queue_to_acquire_time, acquire_to_latch_time, latch_to_present_time"; private static final String SLICE_SQL = - "select " + BASE_COLUMNS + " from gpu_slice where id = %d"; + "select * from " + + "(select " + BASE_COLUMNS + " from frame_slice where id = %d) left join " + + "(select " + STAT_COLUMNS + " from frame_slice " + + "where name = 'PresentFenceSignaled' and frame_number = " + + "(select frame_number from frame_slice where id = %d) " + + "and layer_name = '%s')"; private static final String SLICES_SQL = "select " + BASE_COLUMNS + " from %s " + - "where ts >= %d - dur and ts <= %d order by ts"; - private static final String SUMMARY_SQL = - "select quantum_ts, count(*) from %s " + - "where name = 'PresentFenceSignaled' or name GLOB '*[0-9]*'" + - "group by quantum_ts"; + "where dur >= 0 and ts >= %d - dur and ts <= %d order by ts"; private static final String RANGE_SQL = - "select " + BASE_COLUMNS + " from %s " + - "where ts < %d and ts + dur >= %d and depth >= %d and depth <= %d"; - - private final long trackId; - - public FrameEventsTrack(QueryEngine qe, long trackId) { - super(qe, "sfevents_" + trackId); - this.trackId = trackId; + "select " + BASE_COLUMNS + ", " + STAT_COLUMNS + " from " + + "(select " + BASE_COLUMNS + " from %s) " + + "inner join " + + "(select frame_number, " + STAT_COLUMNS + " from frame_slice " + + "where layer_name = '%s' and name = 'PresentFenceSignaled') " + + "using(frame_number) " + + "where dur >= 0 and ts < %d and ts + dur >= %d and depth >= %d and depth <= %d"; + + private static final long SIGNAL_MARGIN_NS = 10000; + + private final String layerName; + private final String viewName; + private final String eventName; + + public FrameEventsTrack(QueryEngine qe, String layerName, String viewName, String eventName) { + super(qe, "sfevents_" + viewName); + this.layerName = layerName; + this.viewName = viewName; + this.eventName = eventName; } - public static FrameEventsTrack forBuffer(QueryEngine qe, GpuInfo.Buffer buffer) { - return new FrameEventsTrack(qe, buffer.trackId); + public static FrameEventsTrack forFrameEvent(QueryEngine qe, String layerName, + FrameInfo.Event event) { + return new FrameEventsTrack(qe, layerName, event.viewName, event.name); } @Override protected ListenableFuture initialize() { - String slices = tableName("slices"); String window = tableName("window"); - String span = tableName("span"); return qe.queries( - dropTable(span), - dropView(slices), dropTable(window), - createWindow(window), - createView(slices, format(SLICES_VIEW, trackId)), - createSpan(span, window + ", " + slices + " PARTITIONED depth")); + createWindow(window)); } @Override public ListenableFuture computeData(DataRequest req) { Window window = Window.compute(req, 5); - return transformAsync(window.update(qe, tableName("window")), - $ -> window.quantized ? computeSummary(req, window) : computeSlices(req)); + return transformAsync(window.update(qe, tableName("window")), $ -> computeSlices(req)); } private ListenableFuture computeSlices(DataRequest req) { return transformAsync(qe.query(slicesSql(req)), res -> - transform(qe.getAllArgs(res.stream().mapToLong(r -> r.getLong(8))), args -> { + transform(qe.getAllArgs(res.stream().mapToLong(r -> r.getLong(7))), args -> { int rows = res.getNumRows(); - Data data = new Data(req, new long[rows], new long[rows], new long[rows], new int[rows], - new String[rows], new String[rows], new ArgSet[rows]); + Data data = new Data(req, new long[rows], new long[rows], new long[rows], + new int[rows], new String[rows], new long[rows], new String[rows], + new ArgSet[rows]); res.forEachRow((i, row) -> { long start = row.getLong(1); data.ids[i] = row.getLong(0); data.starts[i] = start; data.ends[i] = start + row.getLong(2); - data.categories[i] = row.getString(3); - data.titles[i] = row.getString(4); - data.depths[i] = row.getInt(5); - data.args[i] = args.getOrDefault(row.getLong(8), ArgSet.EMPTY); + data.depths[i] = row.getInt(4); + data.titles[i] = row.getString(3); + data.args[i] = args.getOrDefault(row.getLong(7), ArgSet.EMPTY); + data.frameNumbers[i] = row.getLong(8); + data.layerNames[i] = layerName; }); return data; })); } private String slicesSql(DataRequest req) { - return format(SLICES_SQL, tableName("slices"), req.range.start, req.range.end); - } - - private ListenableFuture computeSummary(DataRequest req, Window w) { - return transform(qe.query(summarySql()), result -> { - Data data = new Data(req, w.bucketSize, new long[w.getNumberOfBuckets()]); - result.forEachRow(($, r) -> data.numEvents[r.getInt(0)] = r.getLong(1)); - return data; - }); - } - - private String summarySql() { - return format(SUMMARY_SQL, tableName("span")); - } - - public ListenableFuture getSlice(long id) { - return transformAsync(expectOneRow(qe.query(sliceSql(id))), r -> - transform(qe.getArgs(r.getLong(8)), args -> buildSlice(r, args))); + return format(SLICES_SQL, viewName, req.range.start, req.range.end); } - protected Slice buildSlice(QueryEngine.Row row, ArgSet args) { - return new Slice(row, args, trackId); + public ListenableFuture getSlice(long id) { + return transformAsync(expectOneRow(qe.query(sliceSql(id, layerName))), r -> + transform(qe.getArgs(r.getLong(7)), args -> new Slices(r, args, layerName, eventName))); } - private static String sliceSql(long id) { - return format(SLICE_SQL, id); + private static String sliceSql(long id, String layerName) { + return format(SLICE_SQL, id, id, layerName); } - public ListenableFuture> getSlices(TimeSpan ts, int minDepth, int maxDepth) { + public ListenableFuture getSlices(TimeSpan ts, int minDepth, int maxDepth) { + // Stats are available only on PresentFenceSignaled events in frame_slice, so we need + // to do a join to get stats. + + // Example + // select id, ts, dur, name, depth, stack_id, parent_stack_id, arg_set_id, frame_number, + // queue_to_acquire_time, acquire_to_latch_time, latch_to_present_time from + // (select id, ts, dur, name, depth, stack_id, parent_stack_id, arg_set_id, frame_number," + // from NavigationBar00_APP) + // inner join + // (select frame_number, queue_to_acquire_time, acquire_to_latch_time, latch_to_present_time + // from frame_slice where layer_name = '%s' and name = 'PresentFenceSignaled') + // using(frame_number) + // where ts < 123 and ts + dur >= 234 and depth >= 0 and depth <= 1 return transform(qe.query(sliceRangeSql(ts, minDepth, maxDepth)), - res -> res.list(($, row) -> buildSlice(row, ArgSet.EMPTY))); + r -> new Slices(r, layerName, eventName)); } private String sliceRangeSql(TimeSpan ts, int minDepth, int maxDepth) { - return format(RANGE_SQL, tableName("slices"), ts.end, ts.start, minDepth, maxDepth); + return format(RANGE_SQL, viewName, layerName, ts.end, ts.start, minDepth, maxDepth); } public static class Data extends Track.Data { - public final Kind kind; - // Summary. - public final long bucketSize; - public final long[] numEvents; - // slices public final long[] ids; public final long[] starts; public final long[] ends; public final int[] depths; public final String[] titles; - public final String[] categories; + public final long[] frameNumbers; + public final String[] layerNames; public final ArgSet[] args; - public static enum Kind { - slices, - summary, - } - - public Data(DataRequest request, long bucketSize, long[] numEvents) { - super(request); - this.kind = Kind.summary; - this.bucketSize = bucketSize; - this.numEvents = numEvents; - this.ids = null; - this.starts = null; - this.ends = null; - this.depths = null; - this.titles = null; - this.categories = null; - this.args = null; - } - public Data(DataRequest request, long[] ids, long[] starts, long[] ends, int[] depths, - String[] titles, String[] categories, ArgSet[] args) { + String[] titles, long[] frameNumbers, String[] layerNames, ArgSet[] args) { super(request); - this.kind = Kind.slices; - this.bucketSize = 0; - this.numEvents = null; this.ids = ids; this.starts = starts; this.ends = ends; this.depths = depths; this.titles = titles; - this.categories = categories; + this.frameNumbers = frameNumbers; + this.layerNames = layerNames; this.args = args; } } - public static class Slice implements Selection { - public final long id; - public final long time; - public final long dur; - public final String name; - public final ArgSet args; - public final long trackId; - - public Slice(long id, long time, long dur, String name, long trackId) { - this.id = id; - this.time = time; - this.dur = dur; - this.name = name; - this.args = ArgSet.EMPTY; - this.trackId = trackId; + public static class Slices implements Selection { + public int count = 0; + public final List ids = Lists.newArrayList(); + public final List times = Lists.newArrayList(); + public final List durs = Lists.newArrayList(); + public final List names = Lists.newArrayList(); + public final List argsets = Lists.newArrayList(); + public final List frameNumbers = Lists.newArrayList(); + public final List layerNames = Lists.newArrayList(); + public final List eventNames = Lists.newArrayList(); + public final List queueToAcquireTimes = Lists.newArrayList(); + public final List acquireToLatchTimes = Lists.newArrayList(); + public final List latchToPresentTimes = Lists.newArrayList(); + public final Set sliceKeys = Sets.newHashSet(); + public final Set selectedFrameNumbers = Sets.newHashSet(); + + public Slices(QueryEngine.Row row, ArgSet args, String layerName, String eventName) { + add(row.getLong(0), row.getLong(1), row.getLong(2), row.getString(3), args, + row.getLong(8), layerName, eventName, row.getLong(9), row.getLong(10), row.getLong(11)); } - public Slice(long id, long time, long dur, String name, ArgSet args, long trackId) { - this.id = id; - this.time = time; - this.dur = dur; - this.name = name; - this.args = args; - this.trackId = trackId; + public Slices(QueryEngine.Result result, String layerName, String eventName) { + result.forEachRow((i, row) -> this.add(row.getLong(0), row.getLong(1), row.getLong(2), + row.getString(3), ArgSet.EMPTY, row.getLong(8), layerName, eventName, + row.getLong(9), row.getLong(10), row.getLong(11))); } - public Slice(QueryEngine.Row row, ArgSet args, long trackId) { - this(row.getLong(0), row.getLong(1), row.getLong(2), row.getString(4), args, trackId); - } - - public Slice(QueryEngine.Row row, long trackId) { - this(row.getLong(0), row.getLong(1), row.getLong(2), row.getString(4), trackId); + private void add(long id, long time, long dur, String name, ArgSet args, long frameNumber, + String layerName, String eventName, long qaTime, long alTime, long lpTime) { + count++; + this.ids.add(id); + this.times.add(time); + this.durs.add(dur); + this.names.add(name); + this.argsets.add(args); + this.frameNumbers.add(frameNumber); + this.layerNames.add(layerName); + this.eventNames.add(eventName); + this.queueToAcquireTimes.add(qaTime); + this.acquireToLatchTimes.add(alTime); + this.latchToPresentTimes.add(lpTime); + this.sliceKeys.add(id); + this.selectedFrameNumbers.add(frameNumber); } @Override @@ -245,147 +230,86 @@ public String getTitle() { } @Override - public boolean contains(Slice.Key key) { - return key.matches(this); + public boolean contains(Long key) { + return sliceKeys.contains(key); } @Override public Composite buildUi(Composite parent, State state) { - return new FrameEventsSelectionView(parent, state, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(Lists.newArrayList(this)); + if (count <= 0) { + return null; + } else { + return new FrameEventsSelectionView(parent, state, this); + } } @Override public void getRange(Consumer span) { - if (dur > 0) { - span.accept(new TimeSpan(time, time + dur)); - } - } - - public static class Key { - public final long time; - public final long dur; - - public Key(long time, long dur) { - this.time = time; - this.dur = dur; - } - - public Key(Slice slice) { - this(slice.time, slice.dur); - } - - public boolean matches(Slice slice) { - return slice.time == time && slice.dur == dur; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (!(obj instanceof Key)) { - return false; + for (int i = 0; i < count; i++) { + if (durs.get(i) > 0) { + span.accept(new TimeSpan(times.get(i), times.get(i) + durs.get(i))); + } else { // Expand the zoom/highlight time range for signal selections whose dur is 0. + span.accept(new TimeSpan(times.get(i), times.get(i) + + durs.get(i)).expand(SIGNAL_MARGIN_NS)); } - Key o = (Key)obj; - return time == o.time && dur == o.dur; } - - @Override - public int hashCode() { - return Long.hashCode(time ^ dur); - } - } - } - - public static class Slices implements Selection { - private final List slices; - private final String title; - public final ImmutableList nodes; - public final ImmutableSet sliceKeys; - - public Slices(List slices, String title, ImmutableList nodes, - ImmutableSet sliceKeys) { - this.slices = slices; - this.title = title; - this.nodes = nodes; - this.sliceKeys = sliceKeys; - } - - @Override - public String getTitle() { - return title; - } - - @Override - public boolean contains(Slice.Key key) { - return sliceKeys.contains(key); } @Override - public Composite buildUi(Composite parent, State state) { - return new FrameEventsMultiSelectionView(parent, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(slices); - } - - @Override - public void getRange(Consumer span) { - for (Slice slice : slices) { - slice.getRange(span); + public Slices combine(Slices other) { + for (int i = 0; i < other.count; i++) { + if (!this.sliceKeys.contains(other.ids.get(i))) { + add(other.ids.get(i), other.times.get(i), other.durs.get(i), other.names.get(i), + other.argsets.get(i), other.frameNumbers.get(i), other.layerNames.get(i), + other.eventNames.get(i), other.queueToAcquireTimes.get(i), + other.acquireToLatchTimes.get(i), other.latchToPresentTimes.get(i)); + } } + return this; } - } - public static class SlicesBuilder implements Selection.Builder { - private final List slices; - private final String title; - private final TreeMap roots = Maps.newTreeMap(); - private final Set sliceKeys = Sets.newHashSet(); - - public SlicesBuilder(List slices) { - this.slices = slices; - String ti = ""; - for (Slice slice : slices) { - ti = slice.getTitle(); - roots.put(slice.id, new Node(slice.name, slice.dur, slice.dur, slice.trackId)); - sliceKeys.add(new Slice.Key(slice)); - } - this.title = ti; + public int getCount() { + return count; } - @Override - public SlicesBuilder combine(SlicesBuilder other) { - this.slices.addAll(other.slices); - roots.putAll(other.roots); - sliceKeys.addAll(other.sliceKeys); - return this; + public Set getSelectedFrameNumbers() { + return selectedFrameNumbers; } + } - @Override - public Selection build() { - return new Slices(slices, title, ImmutableList.copyOf(roots.values()), - ImmutableSet.copyOf(sliceKeys)); + public static Node[] organizeSlicesToNodes(Slices slices) { + TreeMap roots = Maps.newTreeMap(); + for (int i = 0; i < slices.count; i++) { + roots.put(slices.ids.get(i), new Node(slices.names.get(i), slices.frameNumbers.get(i), + slices.durs.get(i), slices.durs.get(i), slices.eventNames.get(i), + slices.layerNames.get(i), slices.queueToAcquireTimes.get(i), + slices.acquireToLatchTimes.get(i), slices.latchToPresentTimes.get(i))); } + return roots.values().stream().toArray(Node[]::new); } public static class Node { public final String name; + public final long frameNumber; public final long dur; public final long self; - public final long trackId; - - public Node(String name, long dur, long self, long id) { + public final String eventName; + public final String layerName; + public final long queueToAcquireTime; + public final long acquireToLatchTime; + public final long latchToPresentTime; + + public Node(String name, long frameNumber, long dur, long self, String eventName, + String layerName, long qaTime, long alTime, long lpTime) { this.name = name; + this.frameNumber = frameNumber; this.dur = dur; this.self = self; - this.trackId = id; + this.eventName = eventName; + this.layerName = layerName; + this.queueToAcquireTime = qaTime; + this.acquireToLatchTime = alTime; + this.latchToPresentTime = lpTime; } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/FrameInfo.java b/gapic/src/main/com/google/gapid/perfetto/models/FrameInfo.java new file mode 100644 index 0000000000..bda2aaf086 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/FrameInfo.java @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.models; + +import static com.google.gapid.perfetto.models.QueryEngine.createView; +import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.perfetto.models.QueryEngine.expectOneRow; +import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.MoreFutures.transformAsync; +import static java.lang.String.format; + +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.Perfetto; + +import java.util.Collections; +import java.util.List; + +/** + * Data about a GPU in the trace. + */ +public class FrameInfo { + public static final FrameInfo NONE = new FrameInfo(Collections.emptyList()); + + private static final String LAYER_NAME_QUERY = + "select distinct layer_name from frame_slice"; + private static final String LAYER_TRACKS_QUERY = + "select name, track_id from gpu_track inner join " + + "(select distinct track_id from frame_slice where layer_name = '%s') t" + + " on gpu_track.id = t.track_id"; + private static final String TRACK_ID_QUERY = + "select group_concat(track_id) from (%s) where name GLOB '%s'"; + + // The following queries use experimental_slice_layout from perfetto. Its a function which takes + // comma separated string of track_ids and stacks overlapping slices vertically. This is useful + // for generating ownership phases where there can be more than one slice (not related) at the + // same time. + + // TODO: (b/158706107) + // Cast name as int for the phases. This is needed because experimental_slice_layout generates + // columns only based on slice_table. frame_slice is a child of slice_table and the extra columns + // such as frame_number and layer_name won't be generated. The alternate is doing a join on + // frame_slice table with slice_id but that query is very expensive in that it visibly slows down + // the loading of tracks. + private static final String PHASE_QUERY = + "select phase.*, cast(name as INT) as frame_number from " + + "(select id, ts, dur, name, layout_depth as depth, stack_id, parent_stack_id, arg_set_id " + + "from experimental_slice_layout where filter_track_ids = (%s)) phase"; + // We don't need layout_depth for display phase because slice1.end = slice2.start in this track + // and the generator will consider this as overlapping and push slice2 to the next depth. + private static final String DISPLAY_PHASE_QUERY = + "select t.*, CAST(name as INT) as frame_number from " + + "(select id, ts, dur, name, depth, stack_id, parent_stack_id, arg_set_id " + + "from experimental_slice_layout where filter_track_ids = (%s)) t"; + + private static final String BUFFERS_QUERY = + "select track_id, name from (%s) where name GLOB '*Buffer*'"; + private static final String BUFFERS_VIEW_QUERY = + "select frame_slice.* from frame_slice join gpu_track " + + "on frame_slice.track_id = gpu_track.id " + + "where gpu_track.id = %d"; + private static final String MAX_DEPTH_QUERY = + "select max(depth) from %s"; + private static final String EVENTS_COUNT_QUERY = + "select COUNT(1) from %s"; + + private static final String DISPLAY_TOOLTIP = + "The time when from was on screen"; + private static final String APP_TOOLTIP = + "The time from when the buffer was dequeued by the app to when it was enqueued back"; + private static final String GPU_TOOLTIP = + "Duration for which the buffer was owned by GPU. This is the time from when the buffer " + + "was sent to GPU to the time when GPU finishes its work on the buffer. " + + "This *does not* mean the time GPU was working solely on the buffer during this time."; + private static final String COMPOSITION_TOOLTIP = + "The time from when SurfaceFlinger latched on to the buffer and sent for composition " + + "to when it was sent to the display"; + + private List layers; + + private FrameInfo(List layers) { + this.layers = layers; + } + + public int layerCount() { + return layers.size(); + } + + public Iterable layers() { + return Iterables.unmodifiableIterable(layers); + } + + public static ListenableFuture listFrames(Perfetto.Data.Builder data) { + List layerNames = Lists.newArrayList(); + List layers = Lists.newArrayList(); + return transformAsync(data.qe.query(LAYER_NAME_QUERY), r -> { + r.forEachRow(($, row) -> { + layerNames.add(row.getString(0)); + }); + if (layerNames.isEmpty()) { + data.setFrame(FrameInfo.NONE); + return Futures.immediateFuture(data); + } + return transform(getLayers(data.qe, layers, layerNames), resultLayers -> { + data.setFrame(new FrameInfo(resultLayers)); + return data; + }); + }); + } + + private static ListenableFuture> getLayers(QueryEngine qe, List layers, + List layerNames) { + List> phases = Lists.newArrayList(); + List> buffers = Lists.newArrayList(); + // The group hierarchy looks like: + // > Layer 1 + // - DISPLAY + // - APP + // - Wait for GPU + // - COMPOSITION + // - > Buffers + // - - Buffer 1 + // - - ... + // - - Buffer n + // ... + // > Layer m + + // For every layer, add the four phases first, then add the buffers. + return transformAsync(createPhases(qe, phases, layerNames, 0), $1 -> { + return transform(createBuffers(qe, buffers, layerNames, 0), $2 -> { + for (int i = 0; i < layerNames.size(); i++) { + layers.add(new Layer(layerNames.get(i), buffers.get(i), phases.get(i))); + } + return layers; + }); + }); + } + + private static ListenableFuture>> createPhases(QueryEngine qe, + List> phases, List layerNames, int idx) { + String layerName = layerNames.get(idx); + // SQL tables cannot start with a numeric or contain special characters. + // Some toast messages do not set the layer name properly. + // For example, "#0" is a valid layer name but cannot be used directly for SQL + String baseName = ("Layer" + layerName).replaceAll("[^A-Za-z0-9]", ""); + List currentLayerPhases = Lists.newArrayList(); + + // Example: + // create view comxxLayer1_APP as + // select phase.*, cast(NAME as INT) as frame_number from + // (select id, ts, dur, name, layout_depth as depth, stack_id, parent_stack_id, arg_set_id + // from experimental_slice_layout where filter_track_ids = + // (select group_concat(track_id) from + // (select name, track_id from gpu_track inner join + // (select distinct track_id from frame_slice where layer_name = 'com.xx.Layer1') t + // on gpu_track.id = t.track_id); + // where name GLOB 'APP_*')) phase + String displayQuery = + displayPhaseQuery(trackIdQuery(layerTracksQuery(layerName),"Display_*")); + String appQuery = phaseQuery(trackIdQuery(layerTracksQuery(layerName), "APP_*")); + String gpuQuery = phaseQuery(trackIdQuery(layerTracksQuery(layerName), "GPU_*")); + String compositionQuery = phaseQuery(trackIdQuery(layerTracksQuery(layerName), "SF_*")); + String displayViewName = baseName + "_DISPLAY"; + String appViewName = baseName + "_APP"; + String gpuViewName = baseName + "_GPU"; + String compositionViewName = baseName + "_COMPOSITION"; + + // we need to determine the max depth of these views here, so that panel creation + // can happen appropriately. + return transformAsync(qe.queries( + dropView(appViewName), + dropView(gpuViewName), + dropView(compositionViewName), + dropView(displayViewName), + createView(appViewName, appQuery), + createView(gpuViewName, gpuQuery), + createView(compositionViewName, compositionQuery), + createView(displayViewName, displayQuery)), $ -> { + return transformAsync(Futures.allAsList( + getMaxDepth(qe, displayViewName), + getMaxDepth(qe, appViewName), + getMaxDepth(qe, gpuViewName), + getMaxDepth(qe, compositionViewName)), depthList -> { + currentLayerPhases.add(new Event("On Display",baseName + "_DISPLAY", depthList.get(0) + 1, + DISPLAY_TOOLTIP)); + currentLayerPhases.add(new Event("Application", baseName + "_APP", depthList.get(1) + 1, + APP_TOOLTIP)); + currentLayerPhases.add(new Event("Wait for GPU", baseName + "_GPU", depthList.get(2) + 1, + GPU_TOOLTIP)); + currentLayerPhases.add(new Event("Composition", baseName + "_COMPOSITION", + depthList.get(3) + 1, COMPOSITION_TOOLTIP)); + phases.add(currentLayerPhases); + if (idx + 1 >= layerNames.size()) { + return Futures.immediateFuture(phases); + } + return createPhases(qe, phases, layerNames, idx + 1); + }); + }); + } + + private static ListenableFuture getMaxDepth(QueryEngine qe, String viewName) { + return transform(expectOneRow(qe.query(maxDepthQuery(viewName))), r -> { + return r.getLong(0); + }); + } + + private static String trackIdQuery(String from, String filter) { + return format(TRACK_ID_QUERY, from, filter); + } + + private static String phaseQuery(String filter) { + return format(PHASE_QUERY, filter); + } + + private static String displayPhaseQuery(String filter) { + return format(DISPLAY_PHASE_QUERY, filter); + } + + private static String maxDepthQuery(String viewName) { + return format(MAX_DEPTH_QUERY, viewName); + } + + private static ListenableFuture>> createBuffers(QueryEngine qe, + List> buffers, List layerNames, int idx) { + String l = layerNames.get(idx); + return transformAsync(qe.query(buffersQuery(layerTracksQuery(l))), res -> { + List currentLayerBuffers = Lists.newArrayList(); + List trackIds = Lists.newArrayList(); + List trackNames = Lists.newArrayList(); + res.forEachRow(($, row) -> { + trackIds.add(row.getLong(0)); + trackNames.add(row.getString(1)); + }); + if (trackIds.isEmpty()) { + return Futures.immediateFuture(buffers); + } + return transformAsync(createBufferViews(qe, trackIds, trackNames,currentLayerBuffers, 0), $ -> { + buffers.add(currentLayerBuffers); + if (idx + 1 >= layerNames.size()) { + return Futures.immediateFuture(buffers); + } + // Create buffers for the next layer. + return createBuffers(qe, buffers, layerNames, idx + 1); + }); + }); + } + + private static ListenableFuture> createBufferViews(QueryEngine qe, List trackIds, + List names, List buffers, int idx) { + long trackId = trackIds.get(idx); + return transformAsync(qe.queries( + dropView("buffer_" + trackId), + createView("buffer_" + trackId, buffersViewQuery(trackId))), $ -> { + return transformAsync(expectOneRow(qe.query(eventsCountQuery("buffer_" + trackId))), r -> { + // Depth of instant events is always 1, the depth query can be avoided here. + buffers.add(new Event(names.get(idx), "buffer_" + trackId, 1, r.getLong(0))); + if (idx + 1 >= trackIds.size()) { + return Futures.immediateFuture(buffers); + } + // Create view for the next buffer. + return createBufferViews(qe, trackIds, names, buffers, idx + 1); + }); + }); + } + + private static String layerTracksQuery(String layerName) { + return format(LAYER_TRACKS_QUERY, layerName); + } + + private static String buffersQuery(String from) { + return format(BUFFERS_QUERY, from); + } + + private static String buffersViewQuery(long filter) { + return format(BUFFERS_VIEW_QUERY, filter); + } + + private static String eventsCountQuery(String viewName) { + return format(EVENTS_COUNT_QUERY, viewName); + } + + public static class Layer { + public final String layerName; + public final long numEvents; + private final List bufferEvents; + private final List phaseEvents; + + public Layer(String layerName, List bufferEvents, List phaseEvents) { + this.layerName = layerName; + this.bufferEvents = bufferEvents; + this.phaseEvents = phaseEvents; + this.numEvents = bufferEvents.stream().mapToLong(e -> e.numEvents).sum(); + } + + public boolean isBufferEventsEmpty() { + return bufferEvents.isEmpty(); + } + + public int bufferEventsCount() { + return bufferEvents.size(); + } + + public Iterable bufferEvents() { + return Iterables.unmodifiableIterable(bufferEvents); + } + + public boolean isPhaseEventsEmpty() { + return phaseEvents.isEmpty(); + } + + public int phaseEventsCount() { + return phaseEvents.size(); + } + + public Iterable phaseEvents() { + return Iterables.unmodifiableIterable(phaseEvents); + } + } + + public static class Event { + public final String name; + public final String viewName; + public final long maxDepth; + public final String tooltip; + public final long numEvents; + + public Event(String name, String viewName, long maxDepth, String tooltip) { + this.name = name; + this.viewName = viewName; + this.maxDepth = maxDepth; + this.tooltip = tooltip; + this.numEvents = 0; + } + + public Event(String name, String viewName, long maxDepth, long numEvents) { + this.name = name; + this.viewName = viewName; + this.maxDepth = maxDepth; + this.tooltip = name; + this.numEvents = numEvents; + } + + public String getDisplay() { + return name; + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/GpuInfo.java b/gapic/src/main/com/google/gapid/perfetto/models/GpuInfo.java index 9e659bb1da..b69f155870 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/GpuInfo.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/GpuInfo.java @@ -29,8 +29,7 @@ * Data about a GPU in the trace. */ public class GpuInfo { - public static final GpuInfo NONE = new GpuInfo(Collections.emptyList(), Collections.emptyList(), - Collections.emptyList()); + public static final GpuInfo NONE = new GpuInfo(Collections.emptyList(), Collections.emptyList()); private static final String MAX_DEPTH_QUERY = "select t.id, t.name, t.scope, max(depth) + 1 " + @@ -40,16 +39,14 @@ public class GpuInfo { private final List queues; private final List vkApiEvents; - private final List buffers; - private GpuInfo(List queues, List vkApiEvents, List buffers) { + private GpuInfo(List queues, List vkApiEvents) { this.queues = queues; this.vkApiEvents = vkApiEvents; - this.buffers = buffers; } public boolean isEmpty() { - return queues.isEmpty() && buffers.isEmpty(); + return queues.isEmpty(); } public int queueCount() { @@ -68,75 +65,41 @@ public Iterable vkApiEvents() { return Iterables.unmodifiableIterable(vkApiEvents); } - public int bufferCount() { - return buffers.size(); - } - - public Iterable buffers() { - return Iterables.unmodifiableIterable(buffers); - } - public static ListenableFuture listGpus(Perfetto.Data.Builder data) { return transform(info(data.qe), gpu -> data.setGpu(gpu)); } private static ListenableFuture info(QueryEngine qe) { - return transform(qe.queries(MAX_DEPTH_QUERY), res -> { - List queues = Lists.newArrayList(); - List vkApiEvents = Lists.newArrayList(); - List buffers = Lists.newArrayList(); - res.forEachRow(($, r) -> { - switch (r.getString(2)) { - case "gpu_render_stage": - queues.add(new Queue(queues.size(), r)); - break; - case "vulkan_events": - vkApiEvents.add(new VkApiEvent(r)); - break; - case "graphics_frame_event": - buffers.add(new Buffer(r)); - break; - } - }); - - // Sort buffers by name, the query is sorted by track id for the queues. - buffers.sort((b1, b2) -> b1.name.compareTo(b2.name)); - return new GpuInfo(queues, vkApiEvents, buffers); - }); + return + transform(qe.queries(MAX_DEPTH_QUERY), res -> { + List queues = Lists.newArrayList(); + List vkApiEvents = Lists.newArrayList(); + res.forEachRow(($, r) -> { + switch (r.getString(2)) { + case "gpu_render_stage": + queues.add(new Queue(r)); + break; + case "vulkan_events": + vkApiEvents.add(new VkApiEvent(r)); + break; + } + }); + return new GpuInfo(queues, vkApiEvents); + }); } public static class Queue { - public final int id; - public final long trackId; - public final int maxDepth; - - public Queue(int id, long trackId, int maxDepth) { - this.id = id; - this.trackId = trackId; - this.maxDepth = maxDepth; - } - - public Queue(int id, QueryEngine.Row row) { - this(id, row.getLong(0), row.getInt(3)); - } - - public String getDisplay() { - return "GPU Queue " + id; - } - } - - public static class VkApiEvent { - public final long trackId; public final String name; + public final long trackId; public final int maxDepth; - public VkApiEvent(long trackId, String name, int maxDepth) { + public Queue(long trackId, String name, int maxDepth) { this.trackId = trackId; this.name = name; this.maxDepth = maxDepth; } - public VkApiEvent(QueryEngine.Row row) { + public Queue(QueryEngine.Row row) { this(row.getLong(0), row.getString(1), row.getInt(3)); } @@ -145,18 +108,18 @@ public String getDisplay() { } } - public static class Buffer { + public static class VkApiEvent { public final long trackId; public final String name; public final int maxDepth; - public Buffer(long trackId, String name, int maxDepth) { + public VkApiEvent(long trackId, String name, int maxDepth) { this.trackId = trackId; this.name = name; this.maxDepth = maxDepth; } - public Buffer(QueryEngine.Row row) { + public VkApiEvent(QueryEngine.Row row) { this(row.getLong(0), row.getString(1), row.getInt(3)); } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java index f2790554c7..63ca72b24f 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/MemorySummaryTrack.java @@ -15,122 +15,44 @@ */ package com.google.gapid.perfetto.models; -import static com.google.gapid.perfetto.models.QueryEngine.createSpan; -import static com.google.gapid.perfetto.models.QueryEngine.createView; -import static com.google.gapid.perfetto.models.QueryEngine.createWindow; -import static com.google.gapid.perfetto.models.QueryEngine.dropTable; -import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.perfetto.models.CounterInfo.needQuantize; import static com.google.gapid.perfetto.views.TrackContainer.single; -import static com.google.gapid.util.MoreFutures.transform; -import static com.google.gapid.util.MoreFutures.transformAsync; -import static java.lang.String.format; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; -import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Perfetto; +import com.google.gapid.perfetto.models.QueryEngine.Row; +import com.google.gapid.perfetto.views.MemorySelectionView; import com.google.gapid.perfetto.views.MemorySummaryPanel; +import com.google.gapid.perfetto.views.State; + +import org.eclipse.swt.widgets.Composite; + +import java.util.function.BinaryOperator; /** * {@link Track} containing the total system memory usage data. */ -public class MemorySummaryTrack extends Track.WithQueryEngine { - private static final String VIEW_SQL = - "select ts, lead(ts) over (order by ts) - ts dur, max(a) total, max(b) unused," + - " max(c) + max(d) + max(e) buffCache " + - "from (select ts," + - " case when track_id = %d then cast(value as int) end a," + - " case when track_id = %d then cast(value as int) end b," + - " case when track_id = %d then cast(value as int) end c," + - " case when track_id = %d then cast(value as int) end d," + - " case when track_id = %d then cast(value as int) end e " + - " from counter where track_id in (%d, %d, %d, %d, %d))" + - "group by ts"; - private static final String SUMMARY_SQL = - "select min(ts), max(ts + dur), cast(avg(total) as int), cast(avg(unused) as int)," + - " cast(avg(buffCache) as int) " + - "from %s group by quantum_ts"; - private static final String COUNTER_SQL = - "select ts, ts + dur, total, unused, buffCache from %s"; - +public class MemorySummaryTrack + extends CombinedCountersTrack { private final long maxTotal; - private final long totalId; - private final long unusedId; - private final long buffersId; - private final long cachedId; - private final long swapCachedId; - - public MemorySummaryTrack(QueryEngine qe, long maxTotal, long totalId, long unusedId, - long buffersId, long cachedId, long swapCachedId) { - super(qe, "mem_sum"); - this.maxTotal = maxTotal; - this.totalId = totalId; - this.unusedId = unusedId; - this.buffersId = buffersId; - this.cachedId = cachedId; - this.swapCachedId = swapCachedId; + + public MemorySummaryTrack(QueryEngine qe, CounterInfo total, CounterInfo unused, + CounterInfo buffers, CounterInfo cached, CounterInfo swapCached) { + super(qe, "mem_sum", new Column[] { + new Column(total.id, "int", "0", "avg"), + new Column(unused.id, "int", "0", "avg"), + new Column(buffers.id, "int", "0", "avg"), + new Column(cached.id, "int", "0", "avg"), + new Column(swapCached.id, "int", "0", "avg"), + }, needQuantize(total, unused, buffers, cached, swapCached)); + this.maxTotal = (long)total.max; } public long getMaxTotal() { return maxTotal; } - @Override - protected ListenableFuture initialize() { - String vals = tableName("vals"); - String span = tableName("span"); - String window = tableName("window"); - return qe.queries( - dropTable(span), - dropTable(window), - dropView(vals), - createView(vals, viewSql()), - createWindow(window), - createSpan(span, vals + ", " + window)); - } - - private String viewSql() { - return format(VIEW_SQL, totalId, unusedId, buffersId, cachedId, swapCachedId, - totalId, unusedId, buffersId, cachedId, swapCachedId); - } - - @Override - protected ListenableFuture computeData(DataRequest req) { - Window win = Window.compute(req, 5); - return transformAsync(win.update(qe, tableName("window")), $ -> computeData(req, win)); - } - - private ListenableFuture computeData(DataRequest req, Window win) { - return transform(qe.query(win.quantized ? summarySql() : counterSQL()), res -> { - int rows = res.getNumRows(); - if (rows == 0) { - return Data.empty(req); - } - - Data data = new Data( - req, new long[rows + 1], new long[rows + 1], new long[rows + 1], new long[rows + 1]); - res.forEachRow((i, r) -> { - data.ts[i] = r.getLong(0); - data.total[i] = r.getLong(2); - data.unused[i] = r.getLong(3); - data.buffCache[i] = r.getLong(4); - }); - data.ts[rows] = res.getLong(rows - 1, 1, 0); - data.total[rows] = data.total[rows - 1]; - data.unused[rows] = data.unused[rows - 1]; - data.buffCache[rows] = data.buffCache[rows - 1]; - return data; - }); - } - - private String summarySql() { - return format(SUMMARY_SQL, tableName("span")); - } - - private String counterSQL() { - return format(COUNTER_SQL, tableName("span")); - } - public static Perfetto.Data.Builder enumerate(Perfetto.Data.Builder data) { ImmutableListMultimap counters = data.getCounters(CounterInfo.Type.Global); CounterInfo total = onlyOne(counters.get("MemTotal")); @@ -143,10 +65,10 @@ public static Perfetto.Data.Builder enumerate(Perfetto.Data.Builder data) { return data; } - MemorySummaryTrack track = new MemorySummaryTrack( - data.qe, (long)total.max, total.id, free.id, buffers.id, cached.id, swapCached.id); + MemorySummaryTrack track = + new MemorySummaryTrack(data.qe, total, free, buffers, cached, swapCached); data.tracks.addTrack(null, track.getId(), "Memory Usage", - single(state -> new MemorySummaryPanel(state, track), true)); + single(state -> new MemorySummaryPanel(state, track), true, false)); return data; } @@ -154,22 +76,86 @@ private static CounterInfo onlyOne(ImmutableList counters) { return (counters.size() != 1) ? null : counters.get(0); } - public static class Data extends Track.Data { - public final long[] ts; + @Override + protected Data createData(DataRequest request, int numRows) { + return new Data(request, numRows); + } + + @Override + protected Values createValues(int numRows, BinaryOperator combiner) { + return new Values(numRows, combiner); + } + + public static class Data extends CombinedCountersTrack.Data { public final long[] total; public final long[] unused; public final long[] buffCache; - public Data(DataRequest request, long[] ts, long[] total, long[] unusued, long[] buffCache) { - super(request); - this.ts = ts; - this.total = total; - this.unused = unusued; - this.buffCache = buffCache; + public Data(DataRequest request, int numRows) { + super(request, numRows); + this.total = new long[numRows]; + this.unused = new long[numRows]; + this.buffCache = new long[numRows]; + } + + @Override + public void set(int idx, QueryEngine.Row row) { + super.set(idx, row); + total[idx] = row.getLong(FIRST_DATA_COLUMN + 0); + unused[idx] = row.getLong(FIRST_DATA_COLUMN + 1); + buffCache[idx] = + row.getLong(FIRST_DATA_COLUMN + 2) + + row.getLong(FIRST_DATA_COLUMN + 3) + + row.getLong(FIRST_DATA_COLUMN + 4); + } + + @Override + public void copyRow(long time, int src, int dst) { + super.copyRow(time, src, dst); + total[dst] = total[src]; + unused[dst] = unused[src]; + buffCache[dst] = buffCache[src]; + } + } + + public static class Values extends CombinedCountersTrack.Values { + public final long[] total; + public final long[] unused; + public final long[] buffCache; + + public Values(int numRows, BinaryOperator combiner) { + super(numRows, combiner); + this.total = new long[numRows]; + this.unused = new long[numRows]; + this.buffCache = new long[numRows]; + } + + @Override + public void set(int idx, Row row) { + super.set(idx, row); + total[idx] = row.getLong(FIRST_DATA_COLUMN + 0); + unused[idx] = row.getLong(FIRST_DATA_COLUMN + 1); + buffCache[idx] = + row.getLong(FIRST_DATA_COLUMN + 2) + + row.getLong(FIRST_DATA_COLUMN + 3) + + row.getLong(FIRST_DATA_COLUMN + 4); + } + + @Override + public void copyFrom(Values other, int src, int dst) { + total[dst] = other.total[src]; + unused[dst] = other.unused[src]; + buffCache[dst] = other.buffCache[src]; + } + + @Override + public String getTitle() { + return "Memory Usage"; } - public static Data empty(DataRequest req) { - return new Data(req, new long[0], new long[0], new long[0], new long[0]); + @Override + public Composite buildUi(Composite parent, State state) { + return new MemorySelectionView(parent, state, this); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/PowerSummaryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/PowerSummaryTrack.java new file mode 100644 index 0000000000..772e958867 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/PowerSummaryTrack.java @@ -0,0 +1,207 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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. + */ + +package com.google.gapid.perfetto.models; + +import static com.google.gapid.perfetto.models.QueryEngine.createSpan; +import static com.google.gapid.perfetto.models.QueryEngine.createView; +import static com.google.gapid.perfetto.models.QueryEngine.createWindow; +import static com.google.gapid.perfetto.models.QueryEngine.dropTable; +import static com.google.gapid.perfetto.models.QueryEngine.dropView; +import static com.google.gapid.perfetto.views.StyleConstants.POWER_RAIL_COUNTER_TRACK_HEIGHT; +import static com.google.gapid.perfetto.views.TrackContainer.group; +import static com.google.gapid.perfetto.views.TrackContainer.single; +import static com.google.gapid.util.MoreFutures.transform; +import static com.google.gapid.util.MoreFutures.transformAsync; +import static java.lang.String.format; + +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.Perfetto; +import com.google.gapid.perfetto.Unit; +import com.google.gapid.perfetto.models.TrackConfig.Group; +import com.google.gapid.perfetto.views.CounterPanel; +import com.google.gapid.perfetto.views.EnergyBreakdownPanel; +import com.google.gapid.perfetto.views.PowerSummaryPanel; +import com.google.gapid.perfetto.views.TitlePanel; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** {@link Track} summarizing the total Power usage. */ +public class PowerSummaryTrack extends Track.WithQueryEngine { + private static String viewSql; + private static int numPowerRailTracks; + public static Unit unit; + public double minValue; + public double maxValue; + + private static final String VIEW_SQL_MONOTONIC = + "select ts + 1 ts, lead(ts) over win - ts dur, lead(value) over win - value value, lead(id)" + + " over win id from counter where track_id = "; + + public PowerSummaryTrack(QueryEngine qe, int numTracks) { + super(qe, "power_sum"); + this.numPowerRailTracks = numTracks; + } + + public int getNumPowerRailTracks() { + return numPowerRailTracks; + } + + public static Perfetto.Data.Builder enumerate(Perfetto.Data.Builder data) { + ImmutableListMultimap counters = data.getCounters(CounterInfo.Type.Global); + List powerRails = + counters.entries().stream() + .filter(entry -> entry.getKey().startsWith("power.rails")) + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + + if (powerRails.size() == 0) { + return data; + } + + // Power Group + viewSql = buildViewSql(powerRails); + PowerSummaryTrack powerSummaryTrack = new PowerSummaryTrack(data.qe, powerRails.size()); + + // Power Rails tracks. + for (CounterInfo powerRail : powerRails) { + powerSummaryTrack.minValue = Math.min(powerSummaryTrack.minValue, powerRail.min); + powerSummaryTrack.maxValue = Math.max(powerSummaryTrack.maxValue, powerRail.max); + unit = powerRail.unit; + CounterTrack powerRailTrack = new CounterTrack(data.qe, powerRail); + data.tracks.addTrack( + powerSummaryTrack.getId(), + powerRailTrack.getId(), + powerRail.name, + single( + state -> new CounterPanel(state, powerRailTrack, POWER_RAIL_COUNTER_TRACK_HEIGHT), + true, + true)); + } + + powerSummaryTrack.minValue = Math.min(0, powerSummaryTrack.minValue); + Group.UiFactory ui = group(state -> new PowerSummaryPanel(state, powerSummaryTrack), false); + data.tracks.addLabelGroup(null, powerSummaryTrack.getId(), "Power Usage", ui); + + // Energy Breakdown + List energyTracks = + data.getCounters(CounterInfo.Type.Energy).entries().stream() + .map(Map.Entry::getValue) + .collect(Collectors.toList()); + + if(energyTracks.size() > 0) { //TODO: handle in a better way + String energyBreakdownGroup = "energy_breakdown_group"; + data.tracks.addLabelGroup( + null, + energyBreakdownGroup, + "Energy Breakdown", + group(state -> new TitlePanel("Energy Breakdown"), true)); + for (CounterInfo energy : energyTracks) { + EnergyBreakdownTrack energyTrack = new EnergyBreakdownTrack(data.qe, energy); + data.tracks.addTrack( + energyBreakdownGroup, + energyTrack.getId(), + energy.name, + single(state -> new EnergyBreakdownPanel(state, energyTrack, 50), true, true)); + } + } + + return data; + } + + private static String buildViewSql(List powerRails) { + StringBuilder sb = + new StringBuilder() + .append( + String.format( + "select aggregateTable.ts ts, sum(aggregateTable.value) totalValue," + + " aggregateTable.dur dur from (%s%d window win as (order by ts) ) as" + + " aggregateTable", + VIEW_SQL_MONOTONIC, powerRails.get(0).id)); + + for (int i = 1; i < numPowerRailTracks; i++) { + sb.append( + String.format( + "left join (%s%d window win as (order by ts) )", + VIEW_SQL_MONOTONIC, powerRails.get(i).id)); + } + + sb.append(" group by ts"); + return sb.toString(); + } + + @Override + protected ListenableFuture initialize() { + String vals = tableName("vals"); + String span = tableName("span"); + String window = tableName("window"); + return qe.queries( + dropTable(span), + dropTable(window), + dropView(vals), + createView(vals, viewSql), + createWindow(window), + createSpan(span, vals + ", " + window)); + } + + @Override + protected ListenableFuture computeData(DataRequest req) { + Window win = Window.quantized(req, 5); + return transformAsync(win.update(qe, tableName("window")), $ -> computeData(req, win)); + } + + private ListenableFuture computeData(DataRequest req, Window win) { + return transform( + qe.query(sql()), + res -> { + int rows = res.getNumRows(); + if (rows == 0) { + return Data.empty(req); + } + + Data data = new Data(req, new long[rows + 1], new double[rows + 1]); + res.forEachRow( + (i, r) -> { + data.ts[i] = r.getLong(0); + data.values[i] = r.getDouble(1); + }); + data.ts[rows] = res.getLong(rows - 1, 1, 0); + data.values[rows] = data.values[rows - 1]; + return data; + }); + } + + private String sql() { + return format(viewSql, tableName("span")); + } + + public static class Data extends Track.Data { + public final long[] ts; + public final double[] values; + + public Data(DataRequest request, long[] ts, double[] values) { + super(request); + this.ts = ts; + this.values = values; + } + + public static Data empty(DataRequest req) { + return new Data(req, new long[0], new double[0]); + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/ProcessMemoryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/ProcessMemoryTrack.java new file mode 100644 index 0000000000..5d9d8fca6e --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/models/ProcessMemoryTrack.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.models; + +import static com.google.gapid.perfetto.models.CounterInfo.needQuantize; + +import com.google.gapid.models.Perfetto; +import com.google.gapid.perfetto.models.QueryEngine.Row; +import com.google.gapid.perfetto.views.MemorySelectionView; +import com.google.gapid.perfetto.views.ProcessMemoryPanel; +import com.google.gapid.perfetto.views.State; +import com.google.gapid.perfetto.views.TrackContainer; + +import org.eclipse.swt.widgets.Composite; + +import java.util.Map; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class ProcessMemoryTrack + extends CombinedCountersTrack { + private final long maxUsage; + private final long maxSwap; + + public ProcessMemoryTrack(QueryEngine qe, long upid, + CounterInfo file, CounterInfo anon, CounterInfo shared, CounterInfo swap) { + super(qe, "mem_" + upid, new Column[] { + file == null ? Column.nil("0") : new Column(file.id, "int", "0", "avg"), + anon == null ? Column.nil("0") : new Column(anon.id, "int", "0", "avg"), + shared == null ? Column.nil("0") : new Column(shared.id, "int", "0", "avg"), + swap == null ? Column.nil("0") : new Column(swap.id, "int", "0", "avg"), + }, needQuantize(file, anon, shared, swap)); + this.maxUsage = (long)( + (file == null ? 0 : file.max) + + (anon == null ? 0 : anon.max) + + (shared == null ? 0 : shared.max)); + this.maxSwap = swap == null ? 0 : (long)swap.max; + } + + public long getMaxUsage() { + return maxUsage; + } + + public long getMaxSwap() { + return maxSwap; + } + + public static Perfetto.Data.Builder enumerate(Perfetto.Data.Builder data, String parent, long upid) { + Map counters = data.getCounters().values().stream() + .filter(c -> c.type == CounterInfo.Type.Process && c.ref == upid && + c.count > 0 && isMemoryCounter(c)) + .collect(Collectors.toMap(c -> c.name, Function.identity())); + if (!counters.isEmpty()) { + ProcessMemoryTrack track = new ProcessMemoryTrack(data.qe, upid, + counters.get("mem.rss.file"), + counters.get("mem.rss.anon"), + counters.get("mem.rss.shmem"), + counters.get("mem.swap")); + data.tracks.addTrack(parent, track.getId(), "Memory Usage", + TrackContainer.single(state -> new ProcessMemoryPanel(state, track), true, true)); + } + + return data; + } + + private static boolean isMemoryCounter(CounterInfo c) { + return "mem.rss.file".equals(c.name) || + "mem.rss.anon".equals(c.name) || + "mem.rss.shmem".equals(c.name) || + "mem.swap".equals(c.name); + } + + @Override + protected Data createData(DataRequest request, int numRows) { + return new Data(request, numRows); + } + + @Override + protected Values createValues(int numRows, BinaryOperator combiner) { + return new Values(numRows, combiner); + } + + public static class Data extends CombinedCountersTrack.Data { + public final long[] file; + public final long[] anon; + public final long[] shared; + public final long[] swap; + + public Data(DataRequest request, int numRows) { + super(request, numRows); + this.file = new long[numRows]; + this.anon = new long[numRows]; + this.shared = new long[numRows]; + this.swap = new long[numRows]; + } + + @Override + public void set(int idx, Row row) { + super.set(idx, row); + file[idx] = row.getLong(FIRST_DATA_COLUMN + 0); + anon[idx] = row.getLong(FIRST_DATA_COLUMN + 1); + shared[idx] = row.getLong(FIRST_DATA_COLUMN + 2); + swap[idx] = row.getLong(FIRST_DATA_COLUMN + 3); + } + + @Override + public void copyRow(long time, int src, int dst) { + super.copyRow(time, src, dst); + file[dst] = file[src]; + anon[dst] = anon[src]; + shared[dst] = shared[src]; + swap[dst] = swap[src]; + } + } + + public static class Values extends CombinedCountersTrack.Values { + public final long[] file; + public final long[] anon; + public final long[] shared; + public final long[] swap; + + public Values(int numRows, BinaryOperator combiner) { + super(numRows, combiner); + this.file = new long[numRows]; + this.anon = new long[numRows]; + this.shared = new long[numRows]; + this.swap = new long[numRows]; + } + + @Override + public void set(int idx, Row row) { + super.set(idx, row); + file[idx] = row.getLong(FIRST_DATA_COLUMN + 0); + anon[idx] = row.getLong(FIRST_DATA_COLUMN + 1); + shared[idx] = row.getLong(FIRST_DATA_COLUMN + 2); + swap[idx] = row.getLong(FIRST_DATA_COLUMN + 3); + } + + @Override + public void copyFrom(Values other, int src, int dst) { + file[dst] = other.file[src]; + anon[dst] = other.anon[src]; + shared[dst] = other.shared[src]; + swap[dst] = other.swap[src]; + } + + @Override + public String getTitle() { + return "Process Memory Usage"; + } + + @Override + public Composite buildUi(Composite parent, State state) { + return new MemorySelectionView(parent, state, this); + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/models/ProcessSummaryTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/ProcessSummaryTrack.java index 8c6d9b5218..be4ffa9e0d 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/ProcessSummaryTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/ProcessSummaryTrack.java @@ -26,7 +26,10 @@ import static java.util.stream.Collectors.joining; import com.google.common.util.concurrent.ListenableFuture; -import com.google.gapid.perfetto.models.CpuTrack.Slice; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.models.CpuTrack.Slices; + +import java.util.Arrays; /** * {@link Track} containing CPU usage data of all threads in a process. @@ -35,9 +38,17 @@ public class ProcessSummaryTrack extends Track.WithQueryEngine= %d"; + private static final String SLICE_RANGE_FOR_IDS_SQL = + "select sched.id, ts, dur, cpu, utid, upid, end_state, priority " + + "from sched left join thread using(utid) " + + "where sched.id in (%s)"; private final int numCpus; private final ProcessInfo process; @@ -76,8 +87,14 @@ protected ListenableFuture computeData(DataRequest req) { private ListenableFuture computeSummary(DataRequest req, Window w) { return transform(qe.query(summarySql(w.bucketSize)), result -> { - Data data = new Data(req, w.bucketSize, new double[w.getNumberOfBuckets()]); - result.forEachRow(($, r) -> data.utilizations[r.getInt(0)] = r.getDouble(1)); + int len = w.getNumberOfBuckets(); + String[] concatedIds = new String[len]; + Arrays.fill(concatedIds, ""); + Data data = new Data(req, w.bucketSize, concatedIds, new double[len]); + result.forEachRow(($, r) -> { + data.concatedIds[r.getInt(0)] = r.getString(1); + data.utilizations[r.getInt(0)] = r.getDouble(2); + }); return data; }); } @@ -107,7 +124,23 @@ private String slicesSql() { return format(SLICES_SQL, tableName("span")); } - public ListenableFuture getSlice(long id) { + public ListenableFuture getSlices(TimeSpan ts) { + return transform(qe.query(sliceRangeSql(ts)), Slices::new); + } + + private String sliceRangeSql(TimeSpan ts) { + return format(SLICE_RANGE_SQL, process.upid, ts.end, ts.start); + } + + public ListenableFuture getSlices(String ids) { + return transform(qe.query(sliceRangeForIdsSql(ids)), Slices::new); + } + + private static String sliceRangeForIdsSql(String ids) { + return format(SLICE_RANGE_FOR_IDS_SQL, ids); + } + + public ListenableFuture getSlice(long id) { return CpuTrack.getSlice(qe, id); } @@ -115,6 +148,7 @@ public static class Data extends Track.Data { public final Kind kind; // Summary. public final long bucketSize; + public final String[] concatedIds; // Concated ids for all cpu slices in a each time bucket. public final double[] utilizations; // Slice. public final long[] ids; @@ -123,10 +157,11 @@ public static class Data extends Track.Data { public final int[] cpus; public final long[] utids; - public Data(DataRequest request, long bucketSize, double[] utilizations) { + public Data(DataRequest request, long bucketSize, String[] concatedIds, double[] utilizations) { super(request); this.kind = Kind.summary; this.bucketSize = bucketSize; + this.concatedIds = concatedIds; this.utilizations = utilizations; this.ids = null; this.starts = null; @@ -145,6 +180,7 @@ public Data( this.cpus = cpus; this.utids = utids; this.bucketSize = 0; + this.concatedIds = null; this.utilizations = null; } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/Selection.java b/gapic/src/main/com/google/gapid/perfetto/models/Selection.java index 7714877b66..6f4b249d25 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/Selection.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/Selection.java @@ -22,13 +22,9 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; -import com.google.gapid.perfetto.models.CounterTrack.Values; -import com.google.gapid.perfetto.models.SliceTrack.Slice; -import com.google.gapid.perfetto.models.ThreadTrack.StateSlice; import com.google.gapid.perfetto.views.MultiSelectionView; import com.google.gapid.perfetto.views.State; -import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import java.util.Iterator; @@ -40,11 +36,11 @@ /** * Data about the current selection in the UI. */ -public interface Selection { +public interface Selection> { public String getTitle(); - public boolean contains(Key key); + public boolean contains(Long key); + public T combine(T other); public Composite buildUi(Composite parent, State state); - public Selection.Builder getBuilder(); public default void getRange(@SuppressWarnings("unused") Consumer span) { /* do nothing */ @@ -54,21 +50,21 @@ public default boolean isEmpty() { return false; } - public static final Selection EMPTY_SELECTION = new EmptySelection(); + public static final Selection EMPTY_SELECTION = new EmptySelection(); @SuppressWarnings("unchecked") - public static Selection emptySelection() { - return (Selection)EMPTY_SELECTION; + public static > T emptySelection() { + return (T)EMPTY_SELECTION; } - public static class EmptySelection implements Selection, Builder> { + public static class EmptySelection implements Selection { @Override public String getTitle() { return ""; } @Override - public boolean contains(K key) { + public boolean contains(Long key) { return false; } @@ -79,21 +75,11 @@ public boolean isEmpty() { @Override public Composite buildUi(Composite parent, State state) { - return new Composite(parent, SWT.NONE); + return null; } @Override - public Selection.Builder getBuilder() { - return this; - } - - @Override - public EmptySelection combine(EmptySelection other) { - return this; - } - - @Override - public Selection build() { + public EmptySelection combine(EmptySelection other) { return this; } } @@ -102,14 +88,14 @@ public Selection build() { * MultiSelection stores selections across different {@link Kind}s. * */ public static class MultiSelection { - private final NavigableMap, Selection> selections; + private final NavigableMap> selections; - public MultiSelection(Kind type, Selection selection) { + public MultiSelection(Kind type, Selection selection) { this.selections = Maps.newTreeMap(); this.selections.put(type, selection); } - public MultiSelection(NavigableMap, Selection> selections) { + public MultiSelection(NavigableMap> selections) { this.selections = selections; } @@ -122,25 +108,22 @@ public Composite buildUi(Composite parent, State state) { } @SuppressWarnings("unchecked") - public Selection getSelection(Kind type) { - return selections.containsKey(type) ? - (Selection) selections.get(type) : Selection.emptySelection(); + public > T getSelection(Kind type) { + return (T)(selections.containsKey(type) ? selections.get(type) : Selection.emptySelection()); } - @SuppressWarnings({ "unchecked", "rawtypes" }) public void addSelection(MultiSelection other) { for (Selection.Kind k : other.selections.keySet()) { - this.addSelection(k, other.selections.get(k)); + this.addSelection(k, other.getSelection(k)); } } - @SuppressWarnings("unchecked") - public > void addSelection(Kind kind, Selection selection) { - Selection old = getSelection(kind); + public void addSelection(Kind kind, Selection selection) { + Selection old = getSelection(kind); if (old == null || old == Selection.EMPTY_SELECTION) { selections.put(kind, selection); } else { - selections.put(kind, ((T)old.getBuilder()).combine((T)selection.getBuilder()).build()); + selections.put(kind, combine(old, selection)); } } @@ -163,61 +146,59 @@ private TimeSpan getRange() { private Selection firstSelection() { return selections.firstEntry().getValue(); } + + @SuppressWarnings("unchecked") + protected static > Selection combine(Selection a, Selection b) { + return ((T)a).combine((T)b); + } } /** * Selection builder for combining selections across different {@link Kind}s. * */ public static class CombiningBuilder { - private final Map, ListenableFuture>> selections = - Maps.newTreeMap(); + private final Map>> selections = Maps.newTreeMap(); @SuppressWarnings("unchecked") - public > void add( - Kind type, ListenableFuture> selection) { - selections.merge(type, selection, (f1, f2) -> transformAsync(f1, r1 -> - transform(f2, r2 -> (((T)r1).combine((T)r2))))); + public > void add(Kind type, ListenableFuture selection) { + selections.merge(type, (ListenableFuture>)selection, (f1, f2) -> + transformAsync(f1, r1 -> transform(f2, r2 -> (MultiSelection.combine(r1, r2))))); } public ListenableFuture build() { return transform(Futures.allAsList(selections.values()), sels -> { - Iterator> keys = selections.keySet().iterator(); - Iterator> vals = sels.iterator(); - TreeMap, Selection> res = Maps.newTreeMap(); + Iterator keys = selections.keySet().iterator(); + Iterator> vals = sels.iterator(); + TreeMap> res = Maps.newTreeMap(); while (keys.hasNext()) { - res.put(keys.next(), vals.next().build()); + res.put(keys.next(), vals.next()); } return new MultiSelection(res); }); } } - /** - * Selection builder for combining selections within a {@link Kind}. - * */ - public static interface Builder> { - public T combine(T other); - public Selection build(); - } - - @SuppressWarnings("unused") - public static class Kind implements Comparable>{ - public static final Kind Thread = new Kind(0); - public static final Kind ThreadState = new Kind(1); - public static final Kind Cpu = new Kind(2); - public static final Kind Gpu = new Kind(3); - public static final Kind VulkanEvent = new Kind(4); - public static final Kind Counter = new Kind(5); - public static final Kind FrameEvents = new Kind(6); - - public int priority; + public static class Kind implements Comparable{ + public static final Kind Thread = new Kind(1000); + public static final Kind ThreadState = new Kind(1010); + public static final Kind Cpu = new Kind(1020); + public static final Kind Async = new Kind(1030); + public static final Kind Gpu = new Kind(1040); + public static final Kind VulkanEvent = new Kind(1050); + public static final Kind Counter = new Kind(1060); + public static final Kind FrameEvents = new Kind(1070); + public static final Kind Memory = new Kind(1080); + public static final Kind Battery = new Kind(1090); + public static final Kind ProcessMemory = new Kind(1100); + + private final int priority; public Kind(int priority) { this.priority = priority; } @Override - public int compareTo(Kind other) { + public int compareTo(Kind other) { return this.priority - other.priority; } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/SliceTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/SliceTrack.java index 5fff22da8b..f6765cf296 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/SliceTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/SliceTrack.java @@ -29,97 +29,109 @@ import static java.util.Collections.emptyList; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.models.QueryEngine.Result; import com.google.gapid.perfetto.models.QueryEngine.Row; -import com.google.gapid.perfetto.views.SliceSelectionView; import com.google.gapid.perfetto.views.SlicesSelectionView; import com.google.gapid.perfetto.views.State; +import com.google.gapid.proto.service.Service; import org.eclipse.swt.widgets.Composite; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.StringJoiner; import java.util.function.Consumer; /** * {@link Track} containing slices. */ public abstract class SliceTrack extends Track {/*extends Track.WithQueryEngine*/ - protected SliceTrack(long trackId) { - super("slices_" + trackId); + protected SliceTrack(String id) { + super(id); } public static SliceTrack forThread(QueryEngine qe, ThreadInfo thread) { - return new WithQueryEngine(qe, "slice", thread.trackId) { + return new SingleTrackWithQueryEngine(qe, "slice", thread.trackId) { @Override - protected Slice buildSlice(Row row, ArgSet args) { - return new Slice.ThreadSlice(row, args, thread); + protected Slices buildSlices(Row row, ArgSet args) { + return new ThreadSlices(row, args, thread); + } + + @Override + protected Slices buildSlices(Result result) { + return new ThreadSlices(result); } }; } + public static SliceTrack forAsync(QueryEngine qe, AsyncInfo async) { + return new AsyncSliceTrack(qe, async); + } + public static SliceTrack forGpuQueue(QueryEngine qe, GpuInfo.Queue queue) { - return new WithQueryEngine(qe, "gpu_slice", queue.trackId) { - // TODO(b/148540258): Remove the copy pasted SliceTrack code and clean up - private final String GPU_COLUMNS = "render_target, render_target_name, render_pass, render_pass_name, command_buffer, command_buffer_name, submission_id"; + return new SingleTrackWithQueryEngine(qe, "gpu_slice", queue.trackId) { + private final QuantizedColumn[] QUANTIZED_COLUMNS = new QuantizedColumn[] { + QuantizedColumn.firstValue("submission_id"), + }; + private final String[] DATA_COLUMNS = new String[] { + "render_target", "render_target_name", "render_pass", "render_pass_name", + "command_buffer", "command_buffer_name", "submission_id" + }; @Override - protected String baseColumns() { - return BASE_COLUMNS + ", " + GPU_COLUMNS; + protected QuantizedColumn[] getExtraQuantizedColumns() { + return QUANTIZED_COLUMNS; } @Override - protected ListenableFuture computeData(DataRequest req) { - Window window = Window.compute(req, 5); - return transformAsync(window.update(qe, tableName("window")), $ -> - window.quantized ? computeQuantSlices(req) : computeSlices(req)); + protected String[] getExtraDataColumns() { + return DATA_COLUMNS; + } + + @Override + protected void appendForQuant(Data data, QueryEngine.Result res) { + data.putExtraLongs("submissionIds", res.stream().mapToLong(r -> r.getLong(6)).toArray()); + } + + @Override + protected void appendForSlices(Data data, Result res) { + int rows = res.getNumRows(); + long[] submissionIds = new long[rows]; + res.forEachRow((i, row) -> { + submissionIds[i] = row.getLong(15); + // Add debug marker to title if it exists + if (data.depths[i] == 0) { + String debugMarker = row.getString(10); + if (!debugMarker.isEmpty()) { + data.titles[i] += "[" + debugMarker + "]"; + } + } + }); + data.putExtraLongs("submissionIds", submissionIds); } - private ListenableFuture computeSlices(DataRequest req) { - return transformAsync(qe.query(slicesSql(req)), res -> - transform(qe.getAllArgs(res.stream().mapToLong(r -> r.getLong(8))), args -> { - int rows = res.getNumRows(); - Data data = new Data(req, new long[rows], new long[rows], new long[rows], new int[rows], - new String[rows], new String[rows], new ArgSet[rows]); - long[] submissionIds = new long[rows]; - res.forEachRow((i, row) -> { - long start = row.getLong(1); - data.ids[i] = row.getLong(0); - data.starts[i] = start; - data.ends[i] = start + row.getLong(2); - data.categories[i] = row.getString(3); - data.titles[i] = row.getString(4); - data.depths[i] = row.getInt(5); - // Add debug marker to title if it exists - if (data.depths[i] == 0) { - String debugMarker = row.getString(10); - if (!debugMarker.isEmpty()) { - data.titles[i] += "[" + debugMarker + "]"; - } - } - data.args[i] = args.getOrDefault(row.getLong(8), ArgSet.EMPTY); - submissionIds[i] = row.getLong(15); - }); - data.putExtraLongs("submissionIds", submissionIds); - return data; - })); + @Override + protected Slices buildSlices(Row row, ArgSet args) { + return new GpuSlices(row, args); } @Override - protected Slice buildSlice(Row row, ArgSet args) { - return new Slice.GpuSlice(row, args); + protected Slices buildSlices(Result result) { + return new GpuSlices(result); } }; } - public abstract ListenableFuture getSlice(long id); - public abstract ListenableFuture> getSlices(TimeSpan ts, int minDepth, int maxDepth); + public abstract ListenableFuture getSlice(long id); + public abstract ListenableFuture getSlices(String concatedId); + public abstract ListenableFuture getSlices(TimeSpan ts, int minDepth, int maxDepth); public static class Data extends Track.Data { public final long[] ids; @@ -130,6 +142,7 @@ public static class Data extends Track.Data { public final String[] categories; public final ArgSet[] args; public Map extraLongs = Maps.newHashMap(); + public Map extraStrings = Maps.newHashMap(); public Data(DataRequest request) { super(request); @@ -154,149 +167,26 @@ public Data(DataRequest request, long[] ids, long[] starts, long[] ends, int[] d this.args = args; } - public void putExtraLongs(String s, long[] longs) { - extraLongs.put(s, longs); - } - - public long[] getExtraLongs(String s) { - return extraLongs.getOrDefault(s, new long[0]); - } - } - - public static abstract class Slice implements Selection { - public final long time; - public final long dur; - public final String category; - public final String name; - public final int depth; - public final long stackId; - public final long parentId; - public final ArgSet args; - - public Slice(long time, long dur, String category, String name, int depth, long stackId, - long parentId, ArgSet args) { - this.time = time; - this.dur = dur; - this.category = category; - this.name = name; - this.depth = depth; - this.stackId = stackId; - this.parentId = parentId; - this.args = args; - } - - public Slice(QueryEngine.Row row, ArgSet args) { - this(row.getLong(1), row.getLong(2), row.getString(3), row.getString(4), row.getInt(5), - row.getLong(6), row.getLong(7), args); - } - - public ThreadInfo getThread() { - return null; - } - - public RenderStageInfo getRenderStageInfo() { - return null; - } - - @Override - public boolean contains(Slice.Key key) { - return key.matches(this); - } - - @Override - public Composite buildUi(Composite parent, State state) { - return new SliceSelectionView(parent, state, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(Lists.newArrayList(this)); + public void putExtraLongs(String name, long[] longs) { + extraLongs.put(name, longs); } - @Override - public void getRange(Consumer span) { - if (dur > 0) { - span.accept(new TimeSpan(time, time + dur)); - } - } - - public static class Key { - public final long time; - public final long dur; - public final int depth; - - public Key(long time, long dur, int depth) { - this.time = time; - this.dur = dur; - this.depth = depth; - } - - public Key(Slice slice) { - this(slice.time, slice.dur, slice.depth); - } - - public boolean matches(Slice slice) { - return slice.time == time && slice.dur == dur && slice.depth == depth; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (!(obj instanceof Key)) { - return false; - } - Key o = (Key)obj; - return time == o.time && dur == o.dur && depth == o.depth; - } - - @Override - public int hashCode() { - return Long.hashCode(time ^ dur) ^ Integer.hashCode(depth); - } + public long[] getExtraLongs(String name) { + return extraLongs.getOrDefault(name, new long[0]); } - public static class ThreadSlice extends Slice { - public final ThreadInfo thread; - - public ThreadSlice(Row row, ArgSet args, ThreadInfo thread) { - super(row, args); - this.thread = thread; - } - - @Override - public String getTitle() { - return "Thread Slices"; - } - - @Override - public ThreadInfo getThread() { - return thread; - } + public void putExtraStrings(String name, String[] strings) { + extraStrings.put(name, strings); } - public static class GpuSlice extends Slice { - private final RenderStageInfo renderStageInfo; - - public GpuSlice(Row row, ArgSet args) { - super(row, args); - renderStageInfo = new RenderStageInfo(row.getLong(9), row.getString(10), row.getLong(11), - row.getString(12), row.getLong(13), row.getString(14), row.getLong(15)); - } - - @Override - public String getTitle() { - return "GPU Queue Events"; - } - - @Override - public RenderStageInfo getRenderStageInfo() { - return renderStageInfo; - } + public String[] getExtraStrings(String name) { + return extraStrings.getOrDefault(name, new String[0]); } } public static class RenderStageInfo { + public static final RenderStageInfo EMPTY = new RenderStageInfo(-1, "", -1, "", -1, "", -1); + public final long frameBufferHandle; public final String frameBufferName; public final long renderPassHandle; @@ -317,18 +207,59 @@ public RenderStageInfo(long frameBufferHandle, String frameBufferName, long rend } } - public static class Slices implements Selection { - private final List slices; + public static class Slices implements Selection { + private int count = 0; + public final List ids = Lists.newArrayList(); + public final List times = Lists.newArrayList(); + public final List durs = Lists.newArrayList(); + public final List categories = Lists.newArrayList(); + public final List names = Lists.newArrayList(); + public final List depths = Lists.newArrayList(); + public final List stackIds = Lists.newArrayList(); + public final List parentIds = Lists.newArrayList(); + public final List argsets = Lists.newArrayList(); // So far only store non-empty argset when there's only 1 slice. + public final Set sliceKeys = Sets.newHashSet(); private final String title; - public final ImmutableList nodes; - public final ImmutableSet sliceKeys; - public Slices(List slices, String title, ImmutableList nodes, - ImmutableSet sliceKeys) { - this.slices = slices; + public Slices(String title) { + this.title = title; + } + + public Slices(QueryEngine.Row row, ArgSet argset, String title) { this.title = title; - this.nodes = nodes; - this.sliceKeys = sliceKeys; + this.add(row, argset); + } + + public Slices(QueryEngine.Result result, String title) { + this.title = title; + result.forEachRow((i, row) -> this.add(row, ArgSet.EMPTY)); + } + + public Slices(List serverSlices, String title) { + this.title = title; + for (Service.ProfilingData.GpuSlices.Slice s : serverSlices) { + this.add(s.getId(), s.getTs(), s.getDur(), "", s.getLabel(), s.getDepth(), -1, -1, ArgSet.EMPTY); + } + } + + private void add(QueryEngine.Row row, ArgSet argset) { + this.add(row.getLong(0), row.getLong(1), row.getLong(2), row.getString(3), row.getString(4), + row.getInt(5), row.getLong(6), row.getLong(7), argset); + } + + private void add(long id, long time, long dur, String category, String name, int depth, + long stackId, long parentId, ArgSet argset) { + this.count++; + this.ids.add(id); + this.times.add(time); + this.durs.add(dur); + this.categories.add(category); + this.names.add(name); + this.depths.add(depth); + this.stackIds.add(stackId); + this.parentIds.add(parentId); + this.argsets.add(argset); + this.sliceKeys.add(id); } @Override @@ -337,80 +268,111 @@ public String getTitle() { } @Override - public boolean contains(Slice.Key key) { + public boolean contains(Long key) { return sliceKeys.contains(key); } @Override public Composite buildUi(Composite parent, State state) { - return new SlicesSelectionView(parent, this); + if (count <= 0) { + return null; + } else { + return new SlicesSelectionView(parent, state, this); + } } @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(slices); + public void getRange(Consumer span) { + for (int i = 0; i < count; i++) { + if (durs.get(i) > 0) { + span.accept(new TimeSpan(times.get(i), times.get(i) + durs.get(i))); + } + } } @Override - public void getRange(Consumer span) { - for (Slice slice : slices) { - slice.getRange(span); + public Slices combine(Slices other) { + for (int i = 0; i < other.count; i++) { + if (!this.sliceKeys.contains(other.ids.get(i))) { + add(other.ids.get(i), other.times.get(i), other.durs.get(i), other.categories.get(i), + other.names.get(i), other.depths.get(i), other.stackIds.get(i), other.parentIds.get(i), + other.argsets.get(i)); + } } + return this; + } + + public int getCount() { + return count; } } - public static class SlicesBuilder implements Selection.Builder { - private final List slices; - private final String title; - private final Map byStack = Maps.newHashMap(); - private final Map> byParent = Maps.newHashMap(); - private final Set roots = Sets.newHashSet(); - private final Set sliceKeys = Sets.newHashSet(); - - public SlicesBuilder(List slices) { - this.slices = slices; - String ti = ""; - for (Slice slice : slices) { - ti = slice.getTitle(); - Node.Builder child = byStack.get(slice.stackId); - if (child == null) { - byStack.put(slice.stackId, child = new Node.Builder(slice.name, slice.stackId, slice.parentId)); - byParent.computeIfAbsent(slice.parentId, $ -> Lists.newArrayList()).add(child); - roots.add(slice.parentId); - } - roots.remove(slice.stackId); - child.add(slice.dur); - sliceKeys.add(new Slice.Key(slice)); + public static class ThreadSlices extends Slices { + public final List threads = Lists.newArrayList(); + + public ThreadSlices(QueryEngine.Row row, ArgSet args, ThreadInfo thread) { + super(row, args, "Thread Slices"); + this.threads.add(thread); + } + + public ThreadSlices(QueryEngine.Result result) { + super(result, "Thread Slices"); + for (int i = 0; i < result.getNumRows(); i++) { + threads.add(ThreadInfo.EMPTY); } - this.title = ti; } - @Override - public SlicesBuilder combine(SlicesBuilder other) { - this.slices.addAll(other.slices); - for (Map.Entry e : other.byStack.entrySet()) { - Node.Builder mine = byStack.get(e.getKey()); - if (mine == null) { - byStack.put(e.getKey(), mine = new Node.Builder(e.getValue())); - byParent.computeIfAbsent(mine.parent, $ -> Lists.newArrayList()).add(mine); - } else { - mine.add(e.getValue()); - } + public ThreadInfo getThreadAt(int index) { + return index < threads.size() ? threads.get(index) : ThreadInfo.EMPTY; + } + } + + public static class GpuSlices extends Slices { + private final List renderStageInfos = Lists.newArrayList(); + + public GpuSlices(Row row, ArgSet args) { + super(row, args, "GPU Queue Events"); + this.renderStageInfos.add(new RenderStageInfo(row.getLong(9), row.getString(10), row.getLong(11), + row.getString(12), row.getLong(13), row.getString(14), row.getLong(15))); + } + + public GpuSlices(QueryEngine.Result result) { + super(result, "GPU Queue Events"); + for (int i = 0; i < result.getNumRows(); i++) { + renderStageInfos.add(RenderStageInfo.EMPTY); } - roots.addAll(other.roots); - sliceKeys.addAll(other.sliceKeys); - return this; } - @Override - public Selection build() { - return new Slices(slices, title, roots.stream() - .filter(not(byStack::containsKey)) - .flatMap(root -> byParent.get(root).stream()) - .map(b -> b.build(byParent)) - .sorted((n1, n2) -> Long.compare(n2.dur, n1.dur)) - .collect(toImmutableList()), ImmutableSet.copyOf(sliceKeys)); + public RenderStageInfo getRenderStageInfoAt(int index) { + return index < renderStageInfos.size() ? renderStageInfos.get(index) : RenderStageInfo.EMPTY; + } + } + + public static Node[] organizeSlicesToNodes(Slices slices) { + Map byStack = Maps.newHashMap(); + Map> byParent = Maps.newHashMap(); + Set roots = Sets.newHashSet(); + + for (int i = 0; i < slices.getCount(); i++) { + String name = slices.names.get(i); + long stackId = slices.stackIds.get(i); + long parentId = slices.parentIds.get(i); + Node.Builder child = byStack.get(stackId); + if (child == null) { + byStack.put(stackId, child = new Node.Builder(name, stackId, parentId)); + byParent.computeIfAbsent(parentId, $ -> Lists.newArrayList()).add(child); + roots.add(parentId); + } + roots.remove(stackId); + child.add(slices.ids.get(i), slices.durs.get(i)); } + + return roots.stream() + .filter(not(byStack::containsKey)) + .flatMap(root -> byParent.get(root).stream()) + .map(b -> b.build(byParent)) + .sorted((n1, n2) -> Long.compare(n2.dur, n1.dur)) + .toArray(Node[]::new); } public static class Node { @@ -430,41 +392,24 @@ public Node(String name, long dur, long self, int count, ImmutableList chi public static class Builder { public final String name; - public final long id; - public final long parent; - private long dur = 0; - private int count = 0; + public final long stackId; + public final long parentId; + private final Map durs = Maps.newHashMap(); // slice_id -> slice_dur. - public Builder(String name, long id, long parent) { + public Builder(String name, long stackId, long parentId) { this.name = name; - this.id = id; - this.parent = parent; - } - - public Builder(Builder other) { - this.name = other.name; - this.id = other.id; - this.parent = other.parent; - this.dur = other.dur; - this.count = other.count; - } - - public long getParent() { - return parent; + this.stackId = stackId; + this.parentId = parentId; } - public void add(long duration) { - dur += duration; - count++; - } - - public void add(Builder other) { - dur += other.dur; - count += other.count; + public void add(long sliceId, long duration) { + durs.put(sliceId, duration); } public Node build(Map> byParent) { - ImmutableList cs = byParent.getOrDefault(id, emptyList()).stream() + long dur = durs.values().stream().mapToLong(d -> d).sum(); + int count = durs.size(); + ImmutableList cs = byParent.getOrDefault(stackId, emptyList()).stream() .map(b -> b.build(byParent)) .sorted((n1, n2) -> Long.compare(n2.dur, n1.dur)) .collect(toImmutableList()); @@ -476,45 +421,22 @@ public Node build(Map> byParent) { } } - private abstract static class WithQueryEngine extends SliceTrack { - protected static final String BASE_COLUMNS = - "id, ts, dur, category, name, depth, stack_id, parent_stack_id, arg_set_id"; - protected final String table; - protected final long trackId; - - private final String SLICES_VIEW = - "select " + baseColumns() + " from %s where track_id = %d"; - private final String SLICES_SQL = - "select " + baseColumns() + " from %s " + - "where ts >= %d - dur and ts <= %d order by ts"; - private static final String SLICES_QUANT_SQL = - "select min(start_ts), max(end_ts), depth, label, max(cnt) from (" + - " select quantum_ts, start_ts, end_ts, depth, label, count(1) cnt, " + - " quantum_ts-row_number() over (partition by depth, label order by quantum_ts) i from (" + - " select quantum_ts, min(ts) over win1 start_ts, max(ts + dur) over win1 end_ts, depth, " + - " substr(group_concat(name) over win1, 0, 101) label" + - " from %s" + - " window win1 as (partition by quantum_ts, depth order by dur desc" + - " range between unbounded preceding and unbounded following))" + - " group by quantum_ts, depth) " + - "group by depth, label, i"; - - private final String SLICE_SQL = - "select " + baseColumns() + " from %s where id = %d"; - private final String SLICE_RANGE_SQL = - "select " + baseColumns() + " from %s " + - "where ts < %d and ts + dur >= %d and depth >= %d and depth <= %d"; - private final QueryEngine qe; - - protected String baseColumns() { - return BASE_COLUMNS; - } - - protected WithQueryEngine(QueryEngine qe, String table, long trackId) { - super(trackId); + public abstract static class WithQueryEngine extends SliceTrack { + private static final String SLICES_SQL = + "select %s from %s where ts >= %d - dur and ts <= %d order by ts"; + private static final String SLICE_SQL = "select %s from %s where id = %d"; + private static final String SLICES_BY_ID_SQL = "select %s from %s where id in (%s)"; + private static final String SLICE_RANGE_SQL = + "select %s from %s where ts < %d and ts + dur >= %d and depth >= %d and depth <= %d"; + + protected static final QuantizedColumn[] NO_QUANTIZED_COLUMNS = new QuantizedColumn[0]; + protected static final String[] NO_DATA_COLUMNS = new String[0]; + + protected final QueryEngine qe; + + public WithQueryEngine(QueryEngine qe, String id) { + super(id); this.qe = qe; - this.table = table; - this.trackId = trackId; } @Override @@ -527,10 +449,12 @@ protected ListenableFuture initialize() { dropView(slices), dropTable(window), createWindow(window), - createView(slices, format(SLICES_VIEW, table, trackId)), + createView(slices, getViewSql()), createSpan(span, window + ", " + slices + " PARTITIONED depth")); } + protected abstract String getViewSql(); + @Override protected ListenableFuture computeData(DataRequest req) { Window window = Window.compute(req, 5); @@ -543,6 +467,7 @@ protected ListenableFuture computeQuantSlices(DataRequest req) { int rows = res.getNumRows(); Data data = new Data(req, new long[rows], new long[rows], new long[rows], new int[rows], new String[rows], new String[rows], new ArgSet[rows]); + String[] concatedIds = new String[rows]; res.forEachRow((i, row) -> { data.ids[i] = -1; data.starts[i] = row.getLong(0); @@ -554,15 +479,52 @@ protected ListenableFuture computeQuantSlices(DataRequest req) { data.titles[i] += "..."; } data.args[i] = ArgSet.EMPTY; + concatedIds[i] = row.getString(5); }); + data.putExtraStrings("concatedIds", concatedIds); + appendForQuant(data, res); return data; }); } private String slicesQuantSql() { - return format(SLICES_QUANT_SQL, tableName("span")); + QuantizedColumn[] extras = getExtraQuantizedColumns(); + + StringBuilder level2 = new StringBuilder().append("select " + + "quantum_ts, min(ts) over win1 start_ts, max(ts + dur) over win1 end_ts, depth, " + + "substr(group_concat(name) over win1, 0, 101) label, id"); + for (QuantizedColumn qc : extras) { + level2.append(", ").append(qc.windowed("win1")).append(" ").append(qc.name); + } + level2.append(" from ").append(tableName("span")) + .append(" window win1 as (partition by quantum_ts, depth order by dur desc " + + "range between unbounded preceding and unbounded following)"); + + StringBuilder level1 = new StringBuilder().append("select " + + "quantum_ts, start_ts, end_ts, depth, label, count(1) cnt, " + + "quantum_ts - row_number() over win2 i, group_concat(id) id"); + for (QuantizedColumn qc : extras) { + level1.append(", ").append(qc.name); + } + level1.append(" from (").append(level2).append(")") + .append(" group by quantum_ts, depth ") + .append(" window win2 as (partition by depth, label order by quantum_ts)"); + + StringBuilder outer = new StringBuilder().append("select " + + "min(start_ts), max(end_ts), depth, label, max(cnt), group_concat(id) id"); + for (QuantizedColumn qc : extras) { + outer.append(", ").append(qc.windowed("win3")); + } + outer.append(" from (").append(level1).append(")") + .append(" group by depth, label, i") + .append(" window win3 as (partition by depth, label, i)"); + + return outer.toString(); } + protected abstract QuantizedColumn[] getExtraQuantizedColumns(); + protected abstract void appendForQuant(Data data, QueryEngine.Result res); + private ListenableFuture computeSlices(DataRequest req) { return transformAsync(qe.query(slicesSql(req)), res -> transform(qe.getAllArgs(res.stream().mapToLong(r -> r.getLong(8))), args -> { @@ -579,38 +541,174 @@ private ListenableFuture computeSlices(DataRequest req) { data.depths[i] = row.getInt(5); data.args[i] = args.getOrDefault(row.getLong(8), ArgSet.EMPTY); }); + appendForSlices(data, res); return data; })); } - protected String slicesSql(DataRequest req) { - return format(SLICES_SQL, tableName("slices"), req.range.start, req.range.end); + private String slicesSql(DataRequest req) { + return format(SLICES_SQL, columns(), tableName("slices"), req.range.start, req.range.end); + } + + protected final String columns() { + StringBuilder sb = new StringBuilder( + "id, ts, dur, category, name, depth, stack_id, parent_stack_id, arg_set_id"); + for (String dc : getExtraDataColumns()) { + sb.append(", ").append(dc); + } + return sb.toString(); } + protected abstract String[] getExtraDataColumns(); + protected abstract void appendForSlices(Data data, QueryEngine.Result res); + @Override - public ListenableFuture getSlice(long id) { + public ListenableFuture getSlice(long id) { return transformAsync(expectOneRow(qe.query(sliceSql(id))), r -> - transform(qe.getArgs(r.getLong(8)), args -> buildSlice(r, args))); + transform(qe.getArgs(r.getLong(8)), args -> buildSlices(r, args))); } - private Slice buildSlice(QueryEngine.Row row) { - return buildSlice(row, ArgSet.EMPTY); + private String sliceSql(long id) { + return format(SLICE_SQL, columns(), tableName("slices"), id); } - protected abstract Slice buildSlice(QueryEngine.Row row, ArgSet args); + @Override + public ListenableFuture getSlices(String concatedId) { + return transform(qe.query(slicesByIdSql(concatedId)), this::buildSlices); + } - private String sliceSql(long id) { - return format(SLICE_SQL, tableName("slices"), id); + private String slicesByIdSql(String concatedId) { + return format(SLICES_BY_ID_SQL, columns(), tableName("slices"), concatedId); } @Override - public ListenableFuture> getSlices(TimeSpan ts, int minDepth, int maxDepth) { - return transform(qe.query(sliceRangeSql(ts, minDepth, maxDepth)), - res -> res.list(($, row) -> buildSlice(row))); + public ListenableFuture getSlices(TimeSpan ts, int minDepth, int maxDepth) { + return transform(qe.query(sliceRangeSql(ts, minDepth, maxDepth)), this::buildSlices); } private String sliceRangeSql(TimeSpan ts, int minDepth, int maxDepth) { - return format(SLICE_RANGE_SQL, tableName("slices"), ts.end, ts.start, minDepth, maxDepth); + return format(SLICE_RANGE_SQL, + columns(), tableName("slices"), ts.end, ts.start, minDepth, maxDepth); + } + + protected abstract Slices buildSlices(QueryEngine.Row row, ArgSet args); + protected abstract Slices buildSlices(QueryEngine.Result result); + + protected static abstract class QuantizedColumn { + public final String name; + + public QuantizedColumn(String name) { + this.name = name; + } + + public static QuantizedColumn firstValue(String name) { + return new QuantizedColumn(name) { + @Override + public String windowed(String window) { + return "first_value(" + name + ") over " + window; + } + }; + } + + public abstract String windowed(String window); + } + } + + + public abstract static class SingleTrackWithQueryEngine extends WithQueryEngine { + private static final String VIEW_SQL = "select %s from %s where track_id = %d"; + + private final String table; + private final long trackId; + + public SingleTrackWithQueryEngine(QueryEngine qe, String table, long trackId) { + super(qe, "slices_" + trackId); + this.table = table; + this.trackId = trackId; + } + + @Override + protected String getViewSql() { + return format(VIEW_SQL, columns(), table, trackId); + } + + @Override + protected QuantizedColumn[] getExtraQuantizedColumns() { + return NO_QUANTIZED_COLUMNS; + } + + @Override + protected void appendForQuant(Data data, Result res) { + // Do nothing. + } + + @Override + protected String[] getExtraDataColumns() { + return NO_DATA_COLUMNS; + } + + @Override + protected void appendForSlices(Data data, Result res) { + // Do nothing. + } + } + + public static class AsyncSliceTrack extends WithQueryEngine { + private final AsyncInfo async; + + public AsyncSliceTrack(QueryEngine qe, AsyncInfo async) { + super(qe, "proc_async_" + async.upid + "_" + async.tracks[0].trackId); + this.async = async; + } + + @Override + protected String getViewSql() { + StringBuilder sb = new StringBuilder().append("select id, ts, dur, category, name, "); + sb.append("depth + case track_id"); + for (AsyncInfo.Track track : async.tracks) { + sb.append(" when ").append(track.trackId).append(" then ").append(track.depthOffset); + } + sb.append(" else 0 end depth, stack_id, parent_stack_id, arg_set_id") + .append(" from internal_slice") + .append(" where track_id in (") + .append(Arrays.stream(async.tracks) + .mapToLong(t -> t.trackId) + .collect(() -> new StringJoiner(", "), + (sj, id) -> sj.add(Long.toString(id)), + StringJoiner::merge)) + .append(")"); + + return sb.toString(); + } + + @Override + protected QuantizedColumn[] getExtraQuantizedColumns() { + return NO_QUANTIZED_COLUMNS; + } + + @Override + protected void appendForQuant(Data data, Result res) { + // Do nothing. + } + + @Override + protected String[] getExtraDataColumns() { + return NO_DATA_COLUMNS; + } + + @Override + protected void appendForSlices(Data data, Result res) { + // Do nothing. + } + + @Override + protected Slices buildSlices(Row row, ArgSet args) { + return new Slices(row, args, "Async Process Events"); + } + + @Override + protected Slices buildSlices(Result result) { + return new Slices(result, "Async Process Events"); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/models/ThreadInfo.java b/gapic/src/main/com/google/gapid/perfetto/models/ThreadInfo.java index 0576e30d9b..2b112fc6db 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/ThreadInfo.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/ThreadInfo.java @@ -31,6 +31,8 @@ * Data about a thread in the trace. */ public class ThreadInfo { + public static final ThreadInfo EMPTY = new ThreadInfo(-1, -1, -1, -1, "", -1, -1); + private static final long MIN_DUR = State.MAX_ZOOM_SPAN_NSEC / 1600; private static final String THREAD_QUERY = "with threads as (" + diff --git a/gapic/src/main/com/google/gapid/perfetto/models/ThreadTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/ThreadTrack.java index 9eca4efa28..cc419e9896 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/ThreadTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/ThreadTrack.java @@ -16,7 +16,6 @@ package com.google.gapid.perfetto.models; import static com.google.gapid.perfetto.models.QueryEngine.createSpan; -import static com.google.gapid.perfetto.models.QueryEngine.createSpanLeftJoin; import static com.google.gapid.perfetto.models.QueryEngine.createView; import static com.google.gapid.perfetto.models.QueryEngine.createWindow; import static com.google.gapid.perfetto.models.QueryEngine.dropTable; @@ -25,8 +24,6 @@ import static com.google.gapid.util.MoreFutures.transformAsync; import static java.lang.String.format; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @@ -34,14 +31,12 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.ThreadState; import com.google.gapid.perfetto.TimeSpan; -import com.google.gapid.perfetto.models.SliceTrack.Slice; +import com.google.gapid.perfetto.models.SliceTrack.Slices; import com.google.gapid.perfetto.views.State; -import com.google.gapid.perfetto.views.ThreadStateSliceSelectionView; import com.google.gapid.perfetto.views.ThreadStateSlicesSelectionView; import org.eclipse.swt.widgets.Composite; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -51,31 +46,15 @@ * {@link Track} containing thread state and slices of a thread. */ public class ThreadTrack extends Track.WithQueryEngine { - private static final String SCHED_VIEW = - "select ts, dur, end_state, row_id from sched where utid = %d"; - private static final String INSTANT_VIEW = - "with" + - " events as (select ts from instants where name = 'sched_wakeup' and ref = %d)," + - " wakeup as (select " + - " min(" + - " coalesce((select min(ts) from events), (select end_ts from trace_bounds))," + - " (select min(ts) from %s)" + - " ) ts union select ts from events)" + - "select ts, lead(ts, 1, (select end_ts from trace_bounds)) over (order by ts) - ts dur " + - "from wakeup"; - private static final String STATE_SPAN_VIEW = - "select ts, dur, case " + - " when end_state is not null then 'r'" + - " when lag(end_state) over ts_win is not null then lag(end_state) over ts_win" + - " else 'R'" + - "end as state, row_id " + - "from %s window ts_win as (order by ts)"; - - private static final String SCHED_SQL = - "select ts, dur, state, row_id from %s where state != 'S' and state != 'x'"; - private static final String SCHED_RANGE_SQL = - "select ts, dur, state from %s where ts < %d and ts + dur >= %d"; + private static final String THREAD_STATE_VIEW = + "select st.ts ts, st.dur dur, st.state state, st.id id, s.id sched_id " + + "from thread_state st left join sched s on (s.cpu = st.cpu and s.ts = st.ts) " + + "where st.utid = %d"; + private static final String THREAD_STATE_SQL = + "select ts, dur, state, id, sched_id from %s where state != 'S' and state != 'x'"; + private static final String THREAD_STATE_RANGE_SQL = + "select ts, dur, state, id, sched_id from %s where ts < %d and ts + dur >= %d"; private final ThreadInfo thread; private final SliceFetcher sliceTrack; @@ -92,25 +71,16 @@ public ThreadInfo getThread() { @Override protected ListenableFuture initialize() { - String wakeup = tableName("wakeup"); - String sched = tableName("sched"); - String spanJoin = tableName("span_join"); - String spanView = tableName("span_view"); + String state = tableName("state"); String span = tableName("span"); String window = tableName("window"); return transformAsync(sliceTrack.initialize(), $ -> qe.queries( dropTable(span), - dropView(spanView), - dropTable(spanJoin), - dropView(sched), - dropView(wakeup), + dropView(state), dropTable(window), createWindow(window), - createView(sched, format(SCHED_VIEW, thread.utid)), - createView(wakeup, format(INSTANT_VIEW, thread.utid, sched)), - createSpanLeftJoin(spanJoin, wakeup + ", " + sched), - createView(spanView, format(STATE_SPAN_VIEW, spanJoin)), - createSpan(span, window + ", " + spanView))); + createView(state, format(THREAD_STATE_VIEW, thread.utid)), + createSpan(span, window + ", " + state))); } @Override @@ -118,69 +88,70 @@ protected ListenableFuture computeData(DataRequest req) { Window window = Window.compute(req); return transformAsync(sliceTrack.computeData(req), slices -> transformAsync(window.update(qe, tableName("window")), $ -> - computeSched(req, slices))); + computeState(req, slices))); } - private ListenableFuture computeSched(DataRequest req, SliceTrack.Data slices) { - return transform(qe.query(schedSql()), res -> { + private ListenableFuture computeState(DataRequest req, SliceTrack.Data slices) { + return transform(qe.query(stateSql()), res -> { int rows = res.getNumRows(); - Data data = new Data(req, new long[rows], new long[rows], new long[rows], + Data data = new Data(req, new long[rows], new long[rows], new long[rows], new long[rows], new ThreadState[rows], slices); res.forEachRow((i, row) -> { long start = row.getLong(0); data.schedStarts[i] = start; data.schedEnds[i] = start + row.getLong(1); data.schedStates[i] = ThreadState.of(row.getString(2)); - data.schedIds[i] = row.getLong(3); + data.ids[i] = row.getLong(3); + data.schedIds[i] = row.getLong(4); }); return data; }); } - private String schedSql() { - return format(SCHED_SQL, tableName("span")); + private String stateSql() { + return format(THREAD_STATE_SQL, tableName("span")); } - public ListenableFuture getSlice(long id) { + public ListenableFuture getSlice(long id) { return sliceTrack.getSlice(id); } - public ListenableFuture getCpuSlice(long id) { + public ListenableFuture getCpuSlice(long id) { return CpuTrack.getSlice(qe, id); } - public ListenableFuture> getSlices(TimeSpan ts, int minDepth, int maxDepth) { + public ListenableFuture getSlices(String concatedId) { + return sliceTrack.getSlices(concatedId); + } + + public ListenableFuture getSlices(TimeSpan ts, int minDepth, int maxDepth) { return sliceTrack.getSlices(ts, minDepth, maxDepth); } - public ListenableFuture> getCpuSlices(TimeSpan ts) { + public ListenableFuture getCpuSlices(TimeSpan ts) { return CpuTrack.getSlices(qe, thread.utid, ts); } - public ListenableFuture> getStates(TimeSpan ts) { - return transform(qe.query(stateRangeSql(ts)), res -> { - List slices = Lists.newArrayList(); - res.forEachRow((i, r) -> slices.add(new StateSlice(r, thread.utid))); - return slices; - }); + public ListenableFuture getStates(TimeSpan ts) { + return transform(qe.query(stateRangeSql(ts)), res -> new StateSlices(res, thread.utid)); } private String stateRangeSql(TimeSpan ts) { - return format(SCHED_RANGE_SQL, tableName("span_view"), ts.end, ts.start); + return format(THREAD_STATE_RANGE_SQL, tableName("state"), ts.end, ts.start); } public static class Data extends Track.Data { - // sched - public final long[] schedIds; + public final long[] ids; + public final long[] schedIds; // only set for Running states. public final long[] schedStarts; public final long[] schedEnds; public final ThreadState[] schedStates; - // slices public final SliceTrack.Data slices; - public Data(DataRequest request, long[] schedIds, long[] schedStarts, long[] schedEnds, - ThreadState[] schedStates, SliceTrack.Data slices) { + public Data(DataRequest request, long[] ids, long[] schedIds, long[] schedStarts, + long[] schedEnds, ThreadState[] schedStates, SliceTrack.Data slices) { super(request); + this.ids = ids; this.schedIds = schedIds; this.schedStarts = schedStarts; this.schedEnds = schedEnds; @@ -189,100 +160,39 @@ public Data(DataRequest request, long[] schedIds, long[] schedStarts, long[] sch } } - public static class StateSlice implements Selection { - public final long time; - public final long dur; - public final long utid; - public final ThreadState state; - - public StateSlice(long time, long dur, long utid, ThreadState state) { - this.time = time; - this.dur = dur; - this.utid = utid; - this.state = state; + public static class StateSlices implements Selection { + protected int count = 0; + public final List ids = Lists.newArrayList(); + public final List schedIds = Lists.newArrayList(); + public final List times = Lists.newArrayList(); + public final List durs = Lists.newArrayList(); + public final List utids = Lists.newArrayList(); + public final List states = Lists.newArrayList(); + public final Set sliceKeys = Sets.newHashSet(); + + public StateSlices(long id, long schedId, long time, long dur, long utid, ThreadState state) { + add(id, schedId, time, dur, utid, state); } - public StateSlice(QueryEngine.Row row, long utid) { - this.time = row.getLong(0); - this.dur = row.getLong(1); - this.utid = utid; - this.state = ThreadState.of(row.getString(2)); + public StateSlices(QueryEngine.Row row, long utid) { + add(row.getLong(3), row.getLong(4), row.getLong(0), row.getLong(1), utid, + ThreadState.of(row.getString(2))); } - @Override - public String getTitle() { - return "Thread State"; - } - - @Override - public boolean contains(StateSlice.Key key) { - return key.matches(this); - } - - @Override - public Composite buildUi(Composite parent, State uiState) { - return new ThreadStateSliceSelectionView(parent, uiState, this); - } - - @Override - public Selection.Builder getBuilder() { - return new StateSlicesBuilder(Lists.newArrayList(this)); - } - - @Override - public void getRange(Consumer span) { - if (dur > 0) { - span.accept(new TimeSpan(time, time + dur)); - } - } - - public static class Key { - public final long time; - public final long dur; - public final long utid; - - public Key(long time, long dur, long utid) { - this.time = time; - this.dur = dur; - this.utid = utid; - } - - public Key(StateSlice slice) { - this(slice.time, slice.dur, slice.utid); - } - - public boolean matches(StateSlice slice) { - return slice.time == time && slice.dur == dur && slice.utid == utid; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) { - return true; - } else if (!(obj instanceof Key)) { - return false; - } - Key o = (Key)obj; - return time == o.time && dur == o.dur && utid == o.utid; - } - - @Override - public int hashCode() { - return Long.hashCode(time ^ dur ^ utid); - } + public StateSlices(QueryEngine.Result result, long utid) { + result.forEachRow((i, row) -> this.add(row.getLong(3), row.getLong(4), row.getLong(0), + row.getLong(1), utid, ThreadState.of(row.getString(2)))); } - } - public static class StateSlices implements Selection { - private final List slices; - public final ImmutableList entries; - public final ImmutableSet sliceKeys; - - public StateSlices(List slices, ImmutableList entries, - ImmutableSet sliceKeys) { - this.slices = slices; - this.entries = entries; - this.sliceKeys = sliceKeys; + private void add(long id, long schedId, long time, long dur, long utid, ThreadState state) { + this.count++; + this.ids.add(id); + this.schedIds.add(schedId); + this.times.add(time); + this.durs.add(dur); + this.utids.add(utid); + this.states.add(state); + this.sliceKeys.add(id); } @Override @@ -291,67 +201,63 @@ public String getTitle() { } @Override - public boolean contains(StateSlice.Key key) { + public boolean contains(Long key) { return sliceKeys.contains(key); } @Override public Composite buildUi(Composite parent, State state) { - return new ThreadStateSlicesSelectionView(parent, this); + if (count <= 0) { + return null; + } else { + return new ThreadStateSlicesSelectionView(parent, state, this); + } } @Override - public Selection.Builder getBuilder() { - return new StateSlicesBuilder(slices); + public void getRange(Consumer span) { + for (int i = 0; i < count; i++) { + if (durs.get(i) > 0) { + span.accept(new TimeSpan(times.get(i), times.get(i) + durs.get(i))); + } + } } @Override - public void getRange(Consumer span) { - for (StateSlice slice : slices) { - slice.getRange(span); + public StateSlices combine(StateSlices other) { + for (int i = 0; i < other.count; i++) { + if (!this.sliceKeys.contains(other.ids.get(i))) { + add(other.ids.get(i), other.schedIds.get(i), other.times.get(i), other.durs.get(i), + other.utids.get(i), other.states.get(i)); + } } + return this; } - public static class Entry { - public final ThreadState state; - public final long totalDur; - - public Entry(ThreadState state, long totalDur) { - this.state = state; - this.totalDur = totalDur; - } + public int getCount() { + return count; } } - public static class StateSlicesBuilder implements Selection.Builder { - private final List slices; - private final Map byState = Maps.newHashMap(); - private final Set sliceKeys = Sets.newHashSet(); - - public StateSlicesBuilder(List slices) { - this.slices = slices; - for (StateSlice slice : slices) { - byState.compute(slice.state, (state, old) -> (old == null) ? slice.dur : old + slice.dur); - sliceKeys.add(new StateSlice.Key(slice)); - } + public static Entry[] organizeSlicesToEntry(StateSlices slices) { + Map> byState = Maps.newHashMap(); // state -> (slice_id -> dur) + for (int i = 0; i < slices.count; i++) { + byState.putIfAbsent(slices.states.get(i), Maps.newHashMap()); + byState.get(slices.states.get(i)).put(slices.ids.get(i), slices.durs.get(i)); } + return byState.entrySet().stream() + .map(e -> new Entry(e.getKey(), e.getValue().values().stream().mapToLong(v -> v).sum())) + .sorted((e1, e2) -> Long.compare(e2.totalDur, e1.totalDur)) + .toArray(Entry[]::new); + } - @Override - public StateSlicesBuilder combine(StateSlicesBuilder other) { - this.slices.addAll(other.slices); - for (Map.Entry e : other.byState.entrySet()) { - byState.merge(e.getKey(), e.getValue(), Long::sum); - } - sliceKeys.addAll(other.sliceKeys); - return this; - } + public static class Entry { + public final ThreadState state; + public final long totalDur; - @Override - public Selection build() { - return new StateSlices(slices, byState.entrySet().stream() - .map(e -> new StateSlices.Entry(e.getKey(), e.getValue())) - .sorted((e1, e2) -> Long.compare(e2.totalDur, e1.totalDur)) - .collect(ImmutableList.toImmutableList()), ImmutableSet.copyOf(sliceKeys)); + public Entry(ThreadState state, long totalDur) { + this.state = state; + this.totalDur = totalDur; } } @@ -369,14 +275,19 @@ public default ListenableFuture computeData(DataRequest req) { } @SuppressWarnings("unused") - public default ListenableFuture getSlice(long id) { + public default ListenableFuture getSlice(long id) { throw new UnsupportedOperationException(); } @SuppressWarnings("unused") - public default ListenableFuture> getSlices( + public default ListenableFuture getSlices(String concatedId) { + return Futures.immediateFuture(null); + } + + @SuppressWarnings("unused") + public default ListenableFuture getSlices( TimeSpan ts, int minDepth, int maxDepth) { - return Futures.immediateFuture(Collections.emptyList()); + return Futures.immediateFuture(null); } public static SliceFetcher forThread(QueryEngine q, ThreadInfo thread) { @@ -397,12 +308,17 @@ public ListenableFuture computeData(DataRequest req) { } @Override - public ListenableFuture getSlice(long id) { + public ListenableFuture getSlice(long id) { return track.getSlice(id); } @Override - public ListenableFuture> getSlices(TimeSpan ts, int minDepth, int maxDepth) { + public ListenableFuture getSlices(String concatedId) { + return track.getSlices(concatedId); + } + + @Override + public ListenableFuture getSlices(TimeSpan ts, int minDepth, int maxDepth) { return track.getSlices(ts, minDepth, maxDepth); } }; diff --git a/gapic/src/main/com/google/gapid/perfetto/models/Track.java b/gapic/src/main/com/google/gapid/perfetto/models/Track.java index a2cb22689d..23d9b0df9a 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/Track.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/Track.java @@ -235,6 +235,14 @@ public static Window compute(DataRequest request, int bucketSizePx) { } } + public static Window compute(DataRequest request, int bucketSizePx, long cutoff) { + if (request.resolution >= cutoff) { + return quantized(request, bucketSizePx); + } else { + return compute(request); + } + } + public static Window quantized(DataRequest request, int bucketSizePx) { long quantum = request.resolution * bucketSizePx; long start = (request.range.start / quantum) * quantum; diff --git a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java index dc9b55f603..7a3c70466c 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/Tracks.java @@ -16,39 +16,68 @@ package com.google.gapid.perfetto.models; import static com.google.gapid.perfetto.views.StyleConstants.DEFAULT_COUNTER_TRACK_HEIGHT; -import static com.google.gapid.perfetto.views.StyleConstants.PROCESS_COUNTER_TRACK_HIGHT; +import static com.google.gapid.perfetto.views.StyleConstants.PROCESS_COUNTER_TRACK_HEIGHT; import static com.google.gapid.perfetto.views.TrackContainer.group; import static com.google.gapid.perfetto.views.TrackContainer.single; +import static java.util.logging.Level.WARNING; import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Perfetto; import com.google.gapid.perfetto.canvas.Panel; import com.google.gapid.perfetto.models.TrackConfig.Group; +import com.google.gapid.perfetto.views.AsyncPanel; import com.google.gapid.perfetto.views.CounterPanel; import com.google.gapid.perfetto.views.CpuFrequencyPanel; import com.google.gapid.perfetto.views.CpuPanel; import com.google.gapid.perfetto.views.CpuSummaryPanel; -import com.google.gapid.perfetto.views.FrameEventsSummaryPanel; +import com.google.gapid.perfetto.views.FrameEventsPanel; import com.google.gapid.perfetto.views.GpuQueuePanel; import com.google.gapid.perfetto.views.ProcessSummaryPanel; import com.google.gapid.perfetto.views.ThreadPanel; import com.google.gapid.perfetto.views.TitlePanel; import com.google.gapid.perfetto.views.VulkanCounterPanel; import com.google.gapid.perfetto.views.VulkanEventPanel; +import com.google.gapid.proto.device.GpuProfiling.GpuCounterDescriptor.GpuCounterGroup; import com.google.gapid.util.Scheduler; + +import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.logging.Logger; import java.util.stream.Collectors; /** * Determines what tracks to show for a trace. */ public class Tracks { + private static final Logger LOG = Logger.getLogger(Tracks.class.getName()); + // CPU usage percentage at which a process/thread is considered idle. private static final double IDLE_PERCENT_CUTOFF = 0.001; // 0.1%. + // Polled counters from the process_stats data source. + private static final ImmutableSet PROC_STATS_COUNTER = ImmutableSet.of( + "mem.virt", "mem.rss", "mem.locked", "oom_score_adj" + ); + + private static final ImmutableMap GPU_COUNTER_GROUP_NAMES = + new ImmutableMap.Builder() + .put(GpuCounterGroup.UNCLASSIFIED, "General Counters") + .put(GpuCounterGroup.SYSTEM, "System Counters") + .put(GpuCounterGroup.VERTICES, "Vertex Counters") + .put(GpuCounterGroup.FRAGMENTS, "Fragment Counters") + .put(GpuCounterGroup.PRIMITIVES, "Primitive Counters") + .put(GpuCounterGroup.MEMORY, "Memory Counters") + .put(GpuCounterGroup.COMPUTE, "Compute Counters") + .build(); private Tracks() { } @@ -58,6 +87,7 @@ public static ListenableFuture enumerate(Perfetto.Data.Bu enumerateCpu(data); enumerateCounters(data); enumerateGpu(data); + enumerateFrame(data); enumerateProcesses(data); enumerateVSync(data); return data; @@ -74,24 +104,24 @@ private static Perfetto.Data.Builder enumerateCpu(Perfetto.Data.Builder data) { for (CpuInfo.Cpu cpu : data.getCpu().cpus()) { CpuTrack track = new CpuTrack(data.qe, cpu); data.tracks.addTrack(summary.getId(), track.getId(), "CPU " + cpu.id, - single(state -> new CpuPanel(state, track), false)); + single(state -> new CpuPanel(state, track), false, false)); if (cpu.hasFrequency()) { CpuFrequencyTrack freqTrack = new CpuFrequencyTrack(data.qe, cpu); data.tracks.addTrack(summary.getId(), freqTrack.getId(), "CPU " + cpu.id + " Frequency", - single(state -> new CpuFrequencyPanel(state, freqTrack), false)); + single(state -> new CpuFrequencyPanel(state, freqTrack), false, false)); hasAnyFrequency = true; } } Group.UiFactory ui = hasAnyFrequency ? - group(state -> new CpuSummaryPanel(state, summary), true, (group, filtered) -> { + group(state -> new CpuSummaryPanel(state, summary), true, (group, showDetails) -> { for (int cpu = 0, track = 0; cpu < data.getCpu().count(); cpu++, track++) { if (data.getCpu().get(cpu).hasFrequency()) { track++; - group.setVisible(track, !filtered); + group.setVisible(track, showDetails); } } - }, true) : + }, false) : group(state -> new CpuSummaryPanel(state, summary), true); data.tracks.addLabelGroup(null, summary.getId(), "CPU Usage", ui); return data; @@ -100,21 +130,41 @@ private static Perfetto.Data.Builder enumerateCpu(Perfetto.Data.Builder data) { public static Perfetto.Data.Builder enumerateCounters(Perfetto.Data.Builder data) { MemorySummaryTrack.enumerate(data); BatterySummaryTrack.enumerate(data); + PowerSummaryTrack.enumerate(data); return data; } public static Perfetto.Data.Builder enumerateGpu(Perfetto.Data.Builder data) { - List counters = data.getCounters(CounterInfo.Type.Gpu).values().stream() + Map counters = data.getCounters(CounterInfo.Type.Gpu).values().stream() .filter(c -> c.count > 0) - .collect(toList()); + .collect(toMap(c -> c.id, c -> c)); - if (counters.isEmpty() && data.getGpu().isEmpty()) { + List gpuMemGlobalCounter = + data.getCounters().values().stream() + .filter(c -> c.type == CounterInfo.Type.Global && + c.ref == 0 /* pid - 0 */ && + c.count > 0 && + c.name.equals("GPU Memory")) + .collect(toList()); + + if (counters.isEmpty() && data.getGpu().isEmpty() && gpuMemGlobalCounter.isEmpty()) { // No GPU data available. return data; } data.tracks.addLabelGroup(null, "gpu", "GPU", group(state -> new TitlePanel("GPU"), true)); + if (!gpuMemGlobalCounter.isEmpty()) { + if (gpuMemGlobalCounter.size() > 1) { + LOG.log(WARNING, "Expected 1 global gpu memory counter. Found " + gpuMemGlobalCounter.size()); + } + CounterInfo counter = gpuMemGlobalCounter.get(0); + CounterTrack track = new CounterTrack(data.qe, counter); + data.tracks.addTrack("gpu", track.getId(), counter.name, + single(state -> new CounterPanel(state, track, DEFAULT_COUNTER_TRACK_HEIGHT), true, + /*right truncate*/ true)); + } + if (data.getGpu().queueCount() > 0) { String parent = "gpu"; if (data.getGpu().queueCount() > 1) { @@ -125,7 +175,7 @@ public static Perfetto.Data.Builder enumerateGpu(Perfetto.Data.Builder data) { for (GpuInfo.Queue queue : data.getGpu().queues()) { SliceTrack track = SliceTrack.forGpuQueue(data.qe, queue); data.tracks.addTrack(parent, track.getId(), queue.getDisplay(), - single(state -> new GpuQueuePanel(state, queue, track), true)); + single(state -> new GpuQueuePanel(state, queue, track), true, false)); } } @@ -139,21 +189,7 @@ public static Perfetto.Data.Builder enumerateGpu(Perfetto.Data.Builder data) { for (GpuInfo.VkApiEvent vkApiEvent : data.getGpu().vkApiEvents()) { VulkanEventTrack track = new VulkanEventTrack(data.qe, vkApiEvent); data.tracks.addTrack(parent, track.getId(), vkApiEvent.getDisplay(), - single(state -> new VulkanEventPanel(state, vkApiEvent, track), true)); - } - } - - if (data.getGpu().bufferCount() > 0) { - String parent = "gpu"; - if (data.getGpu().bufferCount() > 1) { - data.tracks.addLabelGroup("gpu", "sf_events", "Surface Flinger Events", - group(state -> new TitlePanel("Surface Flinger Events"), true)); - parent = "sf_events"; - } - for (GpuInfo.Buffer buffer : data.getGpu().buffers()) { - FrameEventsTrack track = FrameEventsTrack.forBuffer(data.qe, buffer); - data.tracks.addTrack(parent, track.getId(), buffer.getDisplay(), - single(state -> new FrameEventsSummaryPanel(state, buffer, track), true)); + single(state -> new VulkanEventPanel(state, vkApiEvent, track), true, false)); } } @@ -164,10 +200,76 @@ public static Perfetto.Data.Builder enumerateGpu(Perfetto.Data.Builder data) { group(state -> new TitlePanel("GPU Counters"), true)); parent = "gpu_counters"; } - for (CounterInfo counter : counters) { - CounterTrack track = new CounterTrack(data.qe, counter); - data.tracks.addTrack(parent, track.getId(), counter.name, - single(state -> new CounterPanel(state, track, DEFAULT_COUNTER_TRACK_HEIGHT), true)); + Map addedTracks = Maps.newHashMap(); + Multimap groups = data.getGpuCounterGroups(); + if (groups.keySet().size() > 1) { + for (GpuCounterGroup group : GpuCounterGroup.values()) { + if (group == GpuCounterGroup.UNRECOGNIZED) { + continue; + } + Collection grouped_counters = groups.get(Long.valueOf(group.getNumber())); + if (grouped_counters.isEmpty()) { + continue; + } + parent = "gpu_counters_group_" + group.name(); + String name = GPU_COUNTER_GROUP_NAMES.get(group); + data.tracks.addLabelGroup("gpu_counters", parent, name, + group(state -> new TitlePanel(name), true)); + for (Long counter_id : grouped_counters) { + CounterInfo counter = counters.get(counter_id); + if (counter == null) { + continue; + } + CounterTrack track = addedTracks.computeIfAbsent(counter, res -> new CounterTrack(data.qe, counter)); + data.tracks.addTrack(parent, group.name() + track.getId(), counter.name, + single(state -> new CounterPanel(state, track, DEFAULT_COUNTER_TRACK_HEIGHT), true, + /*right truncate*/ true)); + } + } + } else { + for (CounterInfo counter : counters.values()) { + CounterTrack track = new CounterTrack(data.qe, counter); + data.tracks.addTrack(parent, track.getId(), counter.name, + single(state -> new CounterPanel(state, track, DEFAULT_COUNTER_TRACK_HEIGHT), true, + /*right truncate*/ true)); + } + } + } + return data; + } + + public static Perfetto.Data.Builder enumerateFrame(Perfetto.Data.Builder data) { + if (data.getFrame().layerCount() > 0) { + String parent = "sf_events"; + data.tracks.addLabelGroup(null, parent, "Surface Flinger Events", + group(state -> new TitlePanel("Surface Flinger Events"), true)); + + // We assume here that the target application's layer will generally have more + // events than unintended layers from the likes of StatusBar, NavBar etc. + long maxEvents = 0; + for (FrameInfo.Layer layer : data.getFrame().layers()) { + if (layer.numEvents > maxEvents) { + maxEvents = layer.numEvents; + } + } + + for (FrameInfo.Layer layer : data.getFrame().layers()) { + boolean expanded = (layer.numEvents == maxEvents); + data.tracks.addLabelGroup(parent, layer.layerName, layer.layerName, + group(state -> new TitlePanel("Layer - " + layer.layerName), expanded)); + for (FrameInfo.Event phase : layer.phaseEvents()) { + FrameEventsTrack track = FrameEventsTrack.forFrameEvent(data.qe, layer.layerName, phase); + data.tracks.addTrack(layer.layerName, track.getId(), phase.getDisplay(), + single(state -> new FrameEventsPanel(state, phase, track), true, false)); + } + String buffersGroup = "buffers_" + layer.layerName; + data.tracks.addLabelGroup(layer.layerName, buffersGroup, "Buffers", + group(state -> new TitlePanel("Buffers"), false)); + for (FrameInfo.Event buffer : layer.bufferEvents()) { + FrameEventsTrack track = FrameEventsTrack.forFrameEvent(data.qe, layer.layerName, buffer); + data.tracks.addTrack(buffersGroup, track.getId(), buffer.getDisplay(), + single(state -> new FrameEventsPanel(state, buffer, track), true, true)); + } } } return data; @@ -204,13 +306,18 @@ public static Perfetto.Data.Builder enumerateProcesses(Perfetto.Data.Builder dat String groupId = "vulkan_counters_" + process.upid; data.tracks.addLabelGroup(summary.getId(), groupId, "Vulkan Memory Usage", group(state -> new TitlePanel("Vulkan Memory Usage"), true)); - for (CounterInfo counter : counters) { + for (int i = 0; i < counters.size(); i++) { + CounterInfo counter = counters.get(i); + boolean last = i == counters.size() - 1; CounterTrack track = new CounterTrack(data.qe, counter); data.tracks.addTrack(groupId, track.getId(), counter.name, - single(state -> new VulkanCounterPanel(state, track), false)); + single(state -> new VulkanCounterPanel(state, track), last, false)); } } + // For each process, add the memory usage panel if it exists. + ProcessMemoryTrack.enumerate(data, summary.getId(), process.upid); + // For each process, add any other process counters if any exist. counters = data.getCounters().values().stream() .filter(c -> c.type == CounterInfo.Type.Process && c.ref == process.upid && @@ -223,10 +330,30 @@ public static Perfetto.Data.Builder enumerateProcesses(Perfetto.Data.Builder dat data.tracks.addLabelGroup(summary.getId(), parentId, "Process Counters", group(state -> new TitlePanel("Process Counters"), false)); } - for (CounterInfo counter : counters) { + for (int i = 0; i < counters.size(); i++) { + CounterInfo counter = counters.get(i); + boolean last = i == counters.size() - 1; CounterTrack track = new CounterTrack(data.qe, counter); data.tracks.addTrack(parentId, track.getId(), counter.name, - single(state -> new CounterPanel(state, track, PROCESS_COUNTER_TRACK_HIGHT), false)); + single(state -> new CounterPanel( + state, track, PROCESS_COUNTER_TRACK_HEIGHT), last, false)); + } + } + + List asyncs = data.getAsyncs().get(process.upid); + if (!asyncs.isEmpty()) { + String parentId = summary.getId(); + if (asyncs.size() > 3) { + parentId = "proc_asyncs_" + process.upid; + data.tracks.addLabelGroup(summary.getId(), parentId, "Process Async Events", + group(state -> new TitlePanel("Process Async Events"), false)); + } + for (int i = 0; i < asyncs.size(); i++) { + AsyncInfo async = asyncs.get(i); + boolean last = i == asyncs.size() - 1; + SliceTrack track = SliceTrack.forAsync(data.qe, async); + data.tracks.addTrack(parentId, track.getId(), async.name, + single(state -> new AsyncPanel(state, async, track), last, true)); } } @@ -245,11 +372,11 @@ public static Perfetto.Data.Builder enumerateProcesses(Perfetto.Data.Builder dat boolean isIdleThread = hasIdleThreads && track.getThread().totalDur < idleCutoffThread; TrackConfig.Track.UiFactory ui; if (track.getThread().maxDepth == 0) { - ui = single(state -> new ThreadPanel(state, track, false), false); + ui = single(state -> new ThreadPanel(state, track, false), false, false); } else { boolean expanded = !isIdleProcess && !isIdleThread; - ui = single(state -> - new ThreadPanel(state, track, expanded), false, ThreadPanel::setCollapsed, !expanded); + ui = single(state -> new ThreadPanel(state, track, expanded), false, + ThreadPanel::setExpanded, expanded, false); } String threadParent = isIdleThread ? summary.getId() + "_idle" : summary.getId(); data.tracks.addTrack(threadParent, track.getId(), track.getThread().getDisplay(), ui); @@ -299,6 +426,11 @@ private static boolean shouldShowProcessCounter(CounterInfo counter) { return false; } + if (PROC_STATS_COUNTER.contains(counter.name)) { + // The process_stat counters are too infrequent to be helpful. + return false; + } + if ("VSYNC-app".equals(counter.name)) { // VSync has a custom UI. return false; diff --git a/gapic/src/main/com/google/gapid/perfetto/models/VulkanEventTrack.java b/gapic/src/main/com/google/gapid/perfetto/models/VulkanEventTrack.java index 414e187dad..751bae61d1 100644 --- a/gapic/src/main/com/google/gapid/perfetto/models/VulkanEventTrack.java +++ b/gapic/src/main/com/google/gapid/perfetto/models/VulkanEventTrack.java @@ -25,13 +25,11 @@ import static com.google.gapid.util.MoreFutures.transformAsync; import static java.lang.String.format; -import com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.views.State; -import com.google.gapid.perfetto.views.VulkanEventSelectionView; import com.google.gapid.perfetto.views.VulkanEventsSelectionView; import org.eclipse.swt.widgets.Composite; @@ -39,7 +37,6 @@ import java.util.List; import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; public class VulkanEventTrack extends Track.WithQueryEngine { private static final String BASE_COLUMNS = @@ -51,6 +48,14 @@ public class VulkanEventTrack extends Track.WithQueryEngine= %d - dur and ts <= %d order by ts"; + private static final String QUEUE_GROUP_START_SQL = + "select submission_id, min(ts) start from gpu_track t left join gpu_slice s " + + "on (t.id = s.track_id) where t.scope = 'gpu_render_stage' group by submission_id"; + private static final String SLICES_WITH_DIST_SQL = + "with basics as (" + SLICES_SQL + ")," + + "queue_starts as ("+ QUEUE_GROUP_START_SQL + ") " + + "select basics.*, queue_starts.start - ts dist from basics left join queue_starts " + + "on (basics.submission_id = queue_starts.submission_id)"; private static final String SLICE_RANGE_SQL = "select " + BASE_COLUMNS + " from %s " + "where ts < %d and ts + dur >= %d and depth >= %d and depth <= %d"; @@ -84,7 +89,8 @@ private ListenableFuture computeSlices(DataRequest req) { transform(qe.getAllArgs(res.stream().mapToLong(r -> r.getLong(7))), args -> { int rows = res.getNumRows(); Data data = new Data(req, new long[rows], new long[rows], new long[rows], - new String[rows], new int[rows], new long[rows], new long[rows], new ArgSet[rows]); + new String[rows], new int[rows], new long[rows], new long[rows], new ArgSet[rows], + new long[rows]); res.forEachRow((i, row) -> { long start = row.getLong(1); data.ids[i] = row.getLong(0); @@ -95,31 +101,27 @@ private ListenableFuture computeSlices(DataRequest req) { data.commandBuffers[i] = row.getLong(5); data.submissionIds[i] = row.getLong(6); data.args[i] = args.getOrDefault(row.getLong(7), ArgSet.EMPTY); + data.dists[i] = row.getLong(8); }); return data; })); } private String slicesSql(DataRequest req) { - return format(SLICES_SQL, tableName("slices"), req.range.start, req.range.end); + return format(SLICES_WITH_DIST_SQL, tableName("slices"), req.range.start, req.range.end); } - public ListenableFuture getSlice(long id) { + public ListenableFuture getSlice(long id) { return transformAsync(expectOneRow(qe.query(sliceSql(id))), r -> - transform(qe.getArgs(r.getLong(7)), args -> buildSlice(r, args))); + transform(qe.getArgs(r.getLong(7)), args -> new Slices(r, args))); } private String sliceRangeSql(TimeSpan ts, int minDepth, int maxDepth) { return format(SLICE_RANGE_SQL, tableName("slices"), ts.end, ts.start, minDepth, maxDepth); } - public ListenableFuture> getSlices(TimeSpan ts, int minDepth, int maxDepth) { - return transform(qe.query(sliceRangeSql(ts, minDepth, maxDepth)), - res -> res.list(($, row) -> buildSlice(row, ArgSet.EMPTY))); - } - - protected Slice buildSlice(QueryEngine.Row row, ArgSet args) { - return new Slice(row, args); + public ListenableFuture getSlices(TimeSpan ts, int minDepth, int maxDepth) { + return transform(qe.query(sliceRangeSql(ts, minDepth, maxDepth)), Slices::new); } private static String sliceSql(long id) { @@ -135,9 +137,10 @@ public static class Data extends Track.Data { public final long[] commandBuffers; public final long[] submissionIds; public final ArgSet[] args; + public final long[] dists; // Distance between a vulkan event and the linked GPU queue events. public Data(DataRequest request, long[] ids, long[] starts, long[] ends, String[] names, - int[] depths, long[] commandBuffers, long[] submissionIds, ArgSet[] args) { + int[] depths, long[] commandBuffers, long[] submissionIds, ArgSet[] args, long[] dists) { super(request); this.ids = ids; this.starts = starts; @@ -147,78 +150,58 @@ public Data(DataRequest request, long[] ids, long[] starts, long[] ends, String[ this.commandBuffers = commandBuffers; this.submissionIds = submissionIds; this.args = args; + this.dists = dists; } } - public static class Slice implements Selection { - public final long id; - public final long time; - public final long dur; - public final String name; - public final int depth; - public final long commandBuffer; - public final long submissionId; - public final ArgSet args; - - public Slice(long id, long time, long dur, String name, int depth, long commandBuffer, - long submissionId, ArgSet args) { - this.id = id; - this.time = time; - this.dur = dur; - this.name = name; - this.depth = depth; - this.commandBuffer = commandBuffer; - this.submissionId = submissionId; - this.args = args; - } + public static class Slices implements Selection { + private int count = 0; + public final List ids = Lists.newArrayList(); + public final List times = Lists.newArrayList(); + public final List durs = Lists.newArrayList(); + public final List names = Lists.newArrayList(); + public final List depths = Lists.newArrayList(); + public final List commandBuffers = Lists.newArrayList(); + public final List submissionIds = Lists.newArrayList(); + public final List argSets = Lists.newArrayList(); + private final Set submissionIdSet = Sets.newHashSet(); + public final Set sliceKeys = Sets.newHashSet(); - public Slice(QueryEngine.Row row, ArgSet args) { - this(row.getLong(0), row.getLong(1), row.getLong(2), row.getString(3), row.getInt(4), - row.getLong(5), row.getLong(6), args); + public Slices(QueryEngine.Row row, ArgSet argset) { + this.add(row, argset); } - @Override - public String getTitle() { - return "Vulkan API Events"; + public Slices(QueryEngine.Result result) { + result.forEachRow((i, row) -> this.add(row, ArgSet.EMPTY)); } - @Override - public boolean contains(Long key) { - return id == key; + private void add(QueryEngine.Row row, ArgSet argset) { + this.add(row.getLong(0), row.getLong(1), row.getLong(2), row.getString(3), row.getInt(4), + row.getLong(5), row.getLong(6), argset); } - @Override - public Composite buildUi(Composite parent, State state) { - return new VulkanEventSelectionView(parent, state, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(Lists.newArrayList(this)); - } - - @Override - public void getRange(Consumer span) { - if (dur > 0) { - span.accept(new TimeSpan(time, time + dur)); - } + private void add(long id, long time, long dur, String name, int depth, long commandBuffer, + long submissionId, ArgSet argSet) { + this.count++; + this.ids.add(id); + this.times.add(time); + this.durs.add(dur); + this.names.add(name); + this.depths.add(depth); + this.commandBuffers.add(commandBuffer); + this.submissionIds.add(submissionId); + this.argSets.add(argSet); + this.submissionIdSet.add(submissionId); + this.sliceKeys.add(id); } - } - - public static class Slices implements Selection { - public final List slices; - public final ImmutableSet sliceKeys; - private final Set submissionIds; - public Slices(List slices, ImmutableSet sliceKeys) { - this.slices = slices; - this.sliceKeys = sliceKeys; - this.submissionIds = slices.stream().map(s -> s.submissionId).collect(Collectors.toSet()); + public Set getSubmissionIds() { + return submissionIdSet; } @Override public String getTitle() { - return "Vulkan API Event Slices"; + return "Vulkan API Events"; } @Override @@ -228,47 +211,36 @@ public boolean contains(Long key) { @Override public Composite buildUi(Composite parent, State state) { - return new VulkanEventsSelectionView(parent, this); - } - - @Override - public Selection.Builder getBuilder() { - return new SlicesBuilder(slices); + if (count <= 0) { + return null; + } else { + return new VulkanEventsSelectionView(parent, state, this); + } } @Override public void getRange(Consumer span) { - for (Slice slice : slices) { - slice.getRange(span); - } - } - - public Set getSubmissionIds() { - return submissionIds; - } - } - - public static class SlicesBuilder implements Selection.Builder { - private final List slices; - private final Set sliceKeys = Sets.newHashSet(); - - public SlicesBuilder(List slices) { - this.slices = slices; - for (Slice slice : slices) { - sliceKeys.add(slice.id); + for (int i = 0; i < count; i++) { + if (durs.get(i) > 0) { + span.accept(new TimeSpan(times.get(i), times.get(i) + durs.get(i))); + } } } @Override - public SlicesBuilder combine(SlicesBuilder other) { - this.slices.addAll(other.slices); - sliceKeys.addAll(other.sliceKeys); + public Slices combine(Slices other) { + for (int i = 0; i < other.count; i++) { + if (!this.sliceKeys.contains(other.ids.get(i))) { + add(other.ids.get(i), other.times.get(i), other.durs.get(i), other.names.get(i), + other.depths.get(i), other.commandBuffers.get(i), other.submissionIds.get(i), + other.argSets.get(i)); + } + } return this; } - @Override - public Selection build() { - return new Slices(slices, ImmutableSet.copyOf(sliceKeys)); + public int getCount() { + return count; } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/AsyncPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/AsyncPanel.java new file mode 100644 index 0000000000..0fad8d4d04 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/AsyncPanel.java @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.views.Loading.drawLoading; +import static com.google.gapid.perfetto.views.StyleConstants.SELECTION_THRESHOLD; +import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; +import static com.google.gapid.perfetto.views.StyleConstants.colors; + +import com.google.common.collect.Lists; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.Fonts; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.models.AsyncInfo; +import com.google.gapid.perfetto.models.Selection; +import com.google.gapid.perfetto.models.SliceTrack; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.RGBA; +import org.eclipse.swt.widgets.Display; + +import java.util.List; + +public class AsyncPanel extends TrackPanel implements Selectable { + private static final double SLICE_HEIGHT = 25 - 2 * TRACK_MARGIN; + private static final double HOVER_MARGIN = 10; + private static final double HOVER_PADDING = 4; + private static final int BOUNDING_BOX_LINE_WIDTH = 3; + + private final AsyncInfo async; + protected final SliceTrack track; + + protected double mouseXpos, mouseYpos; + protected String hoveredTitle; + protected String hoveredCategory; + protected Size hoveredSize = Size.ZERO; + + public AsyncPanel(State state, AsyncInfo async, SliceTrack track) { + super(state); + this.async = async; + this.track = track; + } + + @Override + public AsyncPanel copy() { + return new AsyncPanel(state, async, track); + } + + @Override + public String getTitle() { + return async.name; + } + + @Override + public String getTooltip() { + return "\\b" + async.name; + } + + @Override + public double getHeight() { + return async.maxDepth * SLICE_HEIGHT; + } + + @Override + protected void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { + ctx.trace("Async", () -> { + SliceTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + drawLoading(ctx, data, state, h); + + if (data == null) { + return; + } + + TimeSpan visible = state.getVisibleTime(); + Selection selected = state.getSelection(Selection.Kind.Async); + List visibleSelected = Lists.newArrayList(); + String[] concatedIds = data.getExtraStrings("concatedIds"); + + for (int i = 0; i < data.starts.length; i++) { + long tStart = data.starts[i]; + long tEnd = data.ends[i]; + int depth = data.depths[i]; + long id = data.ids[i]; + String title = data.titles[i]; + + if (tEnd <= visible.start || tStart >= visible.end) { + continue; + } + double rectStart = state.timeToPx(tStart); + double rectWidth = Math.max(1, state.timeToPx(tEnd) - rectStart); + double y = depth * SLICE_HEIGHT; + + StyleConstants.Gradient color = getSliceColor(data.titles[i]); + color.applyBase(ctx); + ctx.fillRect(rectStart, y, rectWidth, SLICE_HEIGHT); + + // Highlight slice if it's selected. + if (selected.contains(id)) { + visibleSelected.add(new Highlight(color.border, rectStart, y, rectWidth)); + } else if (i < concatedIds.length) { + for (String cId : concatedIds[i].split(",")) { + if (selected.contains(Long.parseLong(cId))) { + visibleSelected.add(new Highlight(color.border, rectStart, y, rectWidth)); + break; + } + } + } + + // Don't render text when we have less than 7px to play with. + if (rectWidth < 7) { + continue; + } + + ctx.setForegroundColor(colors().textMain); + ctx.drawText( + Fonts.Style.Normal, title, rectStart + 2, y + 2, rectWidth - 4, SLICE_HEIGHT - 4); + } + + // Draw bounding rectangles after all the slices are rendered, so that the border is on the top. + for (Highlight highlight : visibleSelected) { + ctx.setForegroundColor(highlight.color); + ctx.drawRect(highlight.x, highlight.y, highlight.w, SLICE_HEIGHT, BOUNDING_BOX_LINE_WIDTH); + } + + if (hoveredTitle != null) { + double cardW = hoveredSize.w + 2 * HOVER_PADDING; + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - HOVER_MARGIN - cardW; + } + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(cardX, mouseYpos, cardW, hoveredSize.h); + + ctx.setForegroundColor(colors().textMain); + ctx.drawText(Fonts.Style.Normal, hoveredTitle, cardX + HOVER_PADDING, + mouseYpos + HOVER_PADDING / 2); + if (!hoveredCategory.isEmpty()) { + ctx.setForegroundColor(colors().textAlt); + ctx.drawText(Fonts.Style.Normal, hoveredCategory, cardX + HOVER_PADDING, + mouseYpos + hoveredSize.h / 2, hoveredSize.h / 2); + } + } + }); + } + + @Override + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + SliceTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + if (data == null) { + return Hover.NONE; + } + + int depth = (int)(y / SLICE_HEIGHT); + if (depth < 0 || depth > async.maxDepth) { + return Hover.NONE; + } + + mouseXpos = x; + mouseYpos = depth * SLICE_HEIGHT; + long t = state.pxToTime(x); + for (int i = 0; i < data.starts.length; i++) { + long tStart = data.starts[i]; + long tEnd = data.ends[i]; + if (data.depths[i] == depth && tStart <= t && t <= tEnd) { + hoveredTitle = data.titles[i]; + hoveredCategory = data.categories[i]; + if (hoveredTitle.isEmpty()) { + if (hoveredCategory.isEmpty()) { + return Hover.NONE; + } + hoveredTitle = hoveredCategory; + hoveredCategory = ""; + } + hoveredTitle += " (" + TimeSpan.timeToString(tEnd - tStart) + ")"; + + hoveredSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, + m.measure(Fonts.Style.Normal, hoveredTitle), + hoveredCategory.isEmpty() ? Size.ZERO : m.measure(Fonts.Style.Normal, hoveredCategory)); + mouseYpos = Math.max(0, Math.min(mouseYpos - (hoveredSize.h - SLICE_HEIGHT) / 2, + (1 + async.maxDepth) * SLICE_HEIGHT - hoveredSize.h)); + long id = data.ids[i]; + String concatedId = i < data.getExtraStrings("concatedIds").length ? + data.getExtraStrings("concatedIds")[i] : ""; + + return new Hover() { + @Override + public Area getRedraw() { + double redrawW = HOVER_MARGIN + hoveredSize.w + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, mouseYpos, redrawW, hoveredSize.h); + } + + @Override + public void stop() { + hoveredTitle = hoveredCategory = null; + } + + @Override + public Cursor getCursor(Display display) { + return (id < 0 && concatedId.isEmpty()) ? null : display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if (id > 0) { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Async, track.getSlice(id)); + } else { + state.setSelection(Selection.Kind.Async, track.getSlice(id)); + } + return true; + } else if (!concatedId.isEmpty()) { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Async, track.getSlices(concatedId)); + } else { + state.setSelection(Selection.Kind.Async, track.getSlices(concatedId)); + } + return true; + } + return false; + } + }; + } + } + return Hover.NONE; + } + + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + int startDepth = (int)(area.y / SLICE_HEIGHT); + int endDepth = (int)((area.y + area.h) / SLICE_HEIGHT); + if (startDepth == endDepth && area.h / SLICE_HEIGHT < SELECTION_THRESHOLD) { + return; + } + if (((startDepth + 1) * SLICE_HEIGHT - area.y) / SLICE_HEIGHT < SELECTION_THRESHOLD) { + startDepth++; + } + if ((area.y + area.h - endDepth * SLICE_HEIGHT) / SLICE_HEIGHT < SELECTION_THRESHOLD) { + endDepth--; + } + if (startDepth > endDepth) { + return; + } + + if (endDepth >= 0) { + if (endDepth >= async.maxDepth) { + endDepth = Integer.MAX_VALUE; + } + builder.add(Selection.Kind.Async, track.getSlices(ts, startDepth, endDepth)); + } + } + + private static class Highlight { + public final RGBA color; + public final double x, y, w; + + public Highlight(RGBA color, double x, double y, double w) { + this.color = color; + this.x = x; + this.y = y; + this.w = w; + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/BatterySelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/BatterySelectionView.java new file mode 100644 index 0000000000..5719577f6d --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/BatterySelectionView.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createTableColumn; +import static com.google.gapid.widgets.Widgets.createTableViewer; +import static com.google.gapid.widgets.Widgets.packColumns; + +import com.google.gapid.perfetto.models.BatterySummaryTrack; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +public class BatterySelectionView extends Composite { + public BatterySelectionView(Composite parent, State state, BatterySummaryTrack.Values sel) { + super(parent, SWT.None); + setLayout(new FillLayout()); + + TableViewer viewer = createTableViewer(this, SWT.NONE); + viewer.setContentProvider(new ArrayContentProvider()); + viewer.setLabelProvider(new LabelProvider()); + + createTableColumn( + viewer, "Time", r -> timeToString(sel.ts[(Integer)r] - state.getTraceTime().start)); + createTableColumn(viewer, "Dur", r -> String.valueOf(sel.dur[(Integer)r])); + createTableColumn(viewer, "Capacity", r -> String.valueOf(sel.capacity[(Integer)r])); + createTableColumn(viewer, "Charge", r -> String.valueOf(sel.charge[(Integer)r])); + createTableColumn(viewer, "Current", r -> String.valueOf(sel.current[(Integer)r])); + + Integer[] rows = new Integer[sel.ts.length]; + for (int i = 0; i < rows.length; i++) { + rows[i] = i; + } + viewer.setInput(rows); + packColumns(viewer.getTable()); + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/BatterySummaryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/BatterySummaryPanel.java index cbcd3dc5ff..8a432c0d3c 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/BatterySummaryPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/BatterySummaryPanel.java @@ -21,21 +21,30 @@ import static com.google.gapid.perfetto.views.StyleConstants.batteryOutGradient; import static com.google.gapid.perfetto.views.StyleConstants.colors; +import com.google.common.collect.Lists; +import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; -import com.google.gapid.perfetto.canvas.Fonts.TextMeasurer; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.BatterySummaryTrack; +import com.google.gapid.perfetto.models.Selection; +import com.google.gapid.perfetto.models.Selection.Kind; -public class BatterySummaryPanel extends TrackPanel { +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.widgets.Display; + +import java.util.List; + +public class BatterySummaryPanel extends TrackPanel implements Selectable { private static final double HEIGHT = 50; private static final double HOVER_MARGIN = 10; private static final double HOVER_PADDING = 4; private static final double CURSOR_SIZE = 5; private static final double LEGEND_SIZE = 8; - private final BatterySummaryTrack track; + protected final BatterySummaryTrack track; protected HoverCard hovered = null; protected double mouseXpos, mouseYpos; @@ -70,6 +79,8 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou } double maxAbs = track.getMaxAbsCurrent(); + Selection selected = state.getSelection(Selection.Kind.Battery); + List visibleSelected = Lists.newArrayList(); // Draw outgoing battery current above the x axis. batteryOutGradient().applyBase(ctx); @@ -83,6 +94,9 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou path.lineTo(nextX, nextY); lastX = nextX; lastY = nextY; + if (selected.contains(data.id[i])) { + visibleSelected.add(i); + } } path.lineTo(lastX, h / 2); path.close(); @@ -107,13 +121,26 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou ctx.fillPath(path); }); + // Draw highlight line after the whole graph is rendered, so that the highlight is on the top. + for (int index : visibleSelected) { + double startX = state.timeToPx(data.ts[index]); + double endX = (index >= data.ts.length - 1) ? startX : state.timeToPx(data.ts[index + 1]); + ctx.setBackgroundColor(data.current[index] > 0 ? + batteryOutGradient().highlight : batteryInGradient().highlight); + ctx.fillRect(startX, h / 2 - h / 2 * data.current[index] / maxAbs - 1, endX - startX, 3); + } + // Draw hovered card. if (hovered != null) { + double cardW = hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE; + double cardX = mouseXpos + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - CURSOR_SIZE / 2 - HOVER_MARGIN - cardW; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(mouseXpos + HOVER_MARGIN, mouseYpos, - hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE, hovered.allSize.h); + ctx.fillRect(cardX, mouseYpos, cardW, hovered.allSize.h); - double x = mouseXpos + HOVER_MARGIN + HOVER_PADDING, y = mouseYpos; + double x = cardX + HOVER_PADDING, y = mouseYpos; double dy = hovered.allSize.h / 2; (hovered.current > 0 ? batteryOutGradient() : batteryInGradient()).applyBase(ctx); @@ -136,8 +163,9 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou } @Override - protected Hover onTrackMouseMove(TextMeasurer m, double x, double y, int mods) { - BatterySummaryTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + BatterySummaryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null || data.ts.length == 0) { return Hover.NONE; } @@ -153,24 +181,56 @@ protected Hover onTrackMouseMove(TextMeasurer m, double x, double y, int mods) { } } + long id = data.id[idx]; hovered = new HoverCard(m, data.capacity[idx], data.charge[idx], data.current[idx]); mouseXpos = x; mouseYpos = (height - 2 * TRACK_MARGIN - hovered.allSize.h) / 2; return new Hover() { @Override public Area getRedraw() { - return new Area(mouseXpos - CURSOR_SIZE, -TRACK_MARGIN, - CURSOR_SIZE + HOVER_MARGIN + hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE, - HEIGHT + 2 * TRACK_MARGIN); + double redrawW = CURSOR_SIZE + HOVER_MARGIN + hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE; + double redrawX = mouseXpos - CURSOR_SIZE / 2; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = mouseXpos + CURSOR_SIZE / 2.0 - redrawW; + + // If the hover card is drawn on the left side of the hover point, when moving the mouse + // from left to right, the right edge of the cursor doesn't seem to get redrawn all the + // time, this looks like a precision issue. This also happens when cursor is now on the + // right side of the hover card, and the mouse moving from right to left there seems to + // be a precision issue on the right edge of the cursor, hence extend the redraw with by + // plusing the radius of the cursor. + redrawW += CURSOR_SIZE / 2; + } + return new Area(redrawX, -TRACK_MARGIN, redrawW, HEIGHT + 2 * TRACK_MARGIN); } @Override public void stop() { hovered = null; } + + @Override + public Cursor getCursor(Display display) { + return display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Kind.Battery, track.getValue(id)); + } else { + state.setSelection(Kind.Battery, track.getValue(id)); + } + return true; + } }; } + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Selection.Kind.Battery, track.getValues(ts)); + } + private static class HoverCard { public static final String REMAINING_POWER_LABEL = "Capacity:"; public static final String CURRENT_OUT_LABEL = "Current Out:"; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CopyablePanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CopyablePanel.java index 93545b2740..fc53349b8b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CopyablePanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CopyablePanel.java @@ -16,7 +16,7 @@ package com.google.gapid.perfetto.views; import com.google.gapid.perfetto.canvas.Area; -import com.google.gapid.perfetto.canvas.Fonts.TextMeasurer; +import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.Panel; import com.google.gapid.perfetto.canvas.PanelGroup; import com.google.gapid.perfetto.canvas.RenderContext; @@ -33,13 +33,13 @@ public interface CopyablePanel> extends Panel { */ public T copy(); - public static class Group implements CopyablePanel { + public static class Group implements CopyablePanel, Panel.Grouper { private final PanelGroup group = new PanelGroup(); @Override public Group copy() { Group copy = new Group(); - for (int i = 0; i < group.size(); i++) { + for (int i = 0; i < group.getPanelCount(); i++) { copy.add(((CopyablePanel)group.getPanel(i)).copy()); copy.group.setVisible(i, group.isVisible(i)); } @@ -50,6 +50,7 @@ public void add(CopyablePanel child) { group.add(child); } + @Override public void setVisible(int idx, boolean visible) { group.setVisible(idx, visible); } @@ -75,13 +76,29 @@ public Dragger onDragStart(double x, double y, int mods) { } @Override - public Hover onMouseMove(TextMeasurer m, double x, double y, int mods) { - return group.onMouseMove(m, x, y, mods); + public Hover onMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + return group.onMouseMove(m, repainter, x, y, mods); } @Override public void visit(Visitor v, Area area) { group.visit(v, area); } + + @Override + public int getPanelCount() { + return group.getPanelCount(); + } + + @Override + public Panel getPanel(int idx) { + return group.getPanel(idx); + } + + @Override + public void setFiltered(int idx, boolean filtered) { + group.setFiltered(idx, filtered); + } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java index fa6f81b5bc..af40aeb8ac 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CounterPanel.java @@ -22,6 +22,7 @@ import static com.google.gapid.util.MoreFutures.transform; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; @@ -29,11 +30,11 @@ import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.CounterInfo; import com.google.gapid.perfetto.models.CounterTrack; -import com.google.gapid.perfetto.models.CounterTrack.Values; import com.google.gapid.perfetto.models.Selection; -import com.google.gapid.perfetto.models.Selection.CombiningBuilder; import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.widgets.Display; import java.util.List; @@ -45,11 +46,13 @@ public class CounterPanel extends TrackPanel implements Selectable protected final CounterTrack track; protected final double trackHeight; protected HoverCard hovered = null; + private CounterTrack.Stats cachedStats; public CounterPanel(State state, CounterTrack track, double trackHeight) { super(state); this.track = track; this.trackHeight = trackHeight; + this.cachedStats = new CounterTrack.Stats(track.getCounter()); } @Override @@ -60,7 +63,9 @@ public CounterPanel copy() { @Override public String getTitle() { CounterInfo info = track.getCounter(); - if (info.type == CounterInfo.Type.Gpu && "gpufreq".equals(info.name)) { + if (info.name.startsWith("power.rails")) { + return info.name.replace("power.rails.","").replace(".", "-"); + } else if (info.type == CounterInfo.Type.Gpu && "gpufreq".equals(info.name)) { return "GPU " + info.ref + " Frequency"; } return track.getCounter().name; @@ -92,9 +97,11 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou } CounterInfo counter = track.getCounter(); - double min = counter.range.min, range = counter.range.range(); - Selection selected = state.getSelection(Selection.Kind.Counter); + double min = counter.range.min; + double range = counter.range.range(); + + Selection selected = state.getSelection(Selection.Kind.Counter); List visibleSelected = Lists.newArrayList(); mainGradient().applyBaseAndBorder(ctx); ctx.path(path -> { @@ -107,7 +114,7 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou path.lineTo(nextX, nextY); lastX = nextX; lastY = nextY; - if (selected.contains(new Values.Key(track.getCounter().name, data.ts[i]))) { + if (selected.contains(data.ids[i])) { visibleSelected.add(i); } } @@ -134,22 +141,28 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou ctx.setBackgroundColor(colors().hoverBackground); double bgH = Math.max(hovered.size.h, trackHeight); - double x = hovered.getTooltipX(); - ctx.fillRect(x, Math.min((trackHeight - bgH) / 2, 0), + // The left x-axis coordinate of HoverCard. + double hx = hovered.mouseX + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (hx >= w - (2 * HOVER_PADDING + hovered.size.w)) { + hx = hovered.mouseX - CURSOR_SIZE / 2 - HOVER_MARGIN - 2 * HOVER_PADDING - hovered.size.w; + } + ctx.fillRect(hx, Math.min((trackHeight - bgH) / 2, 0), 2 * HOVER_PADDING + hovered.size.w, bgH); ctx.setForegroundColor(colors().textMain); - x += HOVER_PADDING; + // The left x-axis coordinate of the left labels. + double x = hx + HOVER_PADDING; y = (trackHeight - hovered.size.h) / 2; + // The difference between the x-axis coordinate of the left labels and the right labels. double dx = hovered.leftWidth + HOVER_PADDING, dy = hovered.size.h / 2; ctx.drawText(Fonts.Style.Normal, "Value:", x, y); - ctx.drawText(Fonts.Style.Normal, "Avg:", x, y + dy); - ctx.drawText(Fonts.Style.Normal, "Min:", x + dx, y); - ctx.drawText(Fonts.Style.Normal, "Max:", x + dx, y + dy); + ctx.drawText(Fonts.Style.Normal, hovered.avgLabel, x, y + dy); + ctx.drawText(Fonts.Style.Normal, hovered.minLabel, x + dx, y); + ctx.drawText(Fonts.Style.Normal, hovered.maxLabel, x + dx, y + dy); - x += hovered.leftWidth; + x = hx + HOVER_PADDING + hovered.leftWidth; ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.label, x, y, dy); ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.avg, x, y + dy, dy); - x = hovered.getTooltipX() + HOVER_PADDING + hovered.size.w; + x = hx + HOVER_PADDING + hovered.size.w; ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.min, x, y, dy); ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.max, x, y + dy, dy); } @@ -157,8 +170,9 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou } @Override - protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - CounterTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + CounterTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null || data.ts.length == 0) { return Hover.NONE; } @@ -179,19 +193,57 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m return Hover.NONE; } - long t = data.ts[idx]; + long id = data.ids[idx]; double startX = state.timeToPx(data.ts[idx]); double endX = (idx >= data.ts.length - 1) ? startX : state.timeToPx(data.ts[idx + 1]); - hovered = new HoverCard(m, track.getCounter(), data.values[idx], startX, endX, x); + hovered = new HoverCard( + m, track.getCounter(), getStats(repainter), data.values[idx], startX, endX, x); return new Hover() { @Override public Area getRedraw() { - double start = Math.min(hovered.mouseX - CURSOR_SIZE / 2, startX); - double end = Math.max( - hovered.getTooltipX() + HOVER_PADDING + hovered.size.w + HOVER_PADDING, - endX); - return new Area(start, -TRACK_MARGIN, end - start, trackHeight + 2 * TRACK_MARGIN); + // The area of the sample can be larger than the hover card. Hence the redraw area needs to + // at least cover the whole sample because when we move between samples, the highlight of + // the previous sample needs to be redrawn without highlight. If the redraw area only + // covers the hover card then the area that is not intersected with the hover card will + // not be redrawn, and hence the highlight remains in that area. + + // First, calculate the default left boundary x-axis coordinate and width of the + // redrawn area. + final double defaultX = hovered.mouseX - CURSOR_SIZE / 2; + final double defaultW = CURSOR_SIZE + HOVER_MARGIN + HOVER_PADDING + hovered.size.w + + HOVER_PADDING; + + // Assuming the hover card is drawn on the right of the hover point. + + // Determine the x-axis coordinate of the left boundary of the redrawn area. + double redrawLx = Math.min(defaultX, startX); + + // Determine the x-axis coordinate of the right boundary of the redrawn area by comparing + // the right end of the sample with the right boundary of the default redrawn area. + double redrawRx = Math.max(defaultX + defaultW, endX); + + // Calculate the real redrawn width. + double redrawW = redrawRx - redrawLx; + + if (defaultX >= state.getWidth() - defaultW) { + + // re-calculate the left boundary of the redrawn area by comparing the start end of the + // sample with the left boundary of the default redrawn area when the hover card is drawn + // on the left side of the hover point. + redrawLx = Math.min(startX, hovered.mouseX + CURSOR_SIZE / 2 - defaultW); + + // re-calculate the right boundary of the redrawn area by comparing the end of the sample + // with the right boundary of the default redrawn area when the hover card is drawn on + // the left side of the hover point. + redrawRx = Math.max(hovered.mouseX + CURSOR_SIZE / 2, endX); + + // Finally, re-calculate the redrawn width, plus radius of the cursor to avoid the + // precision issue at the right edge of the cursor. + redrawW = redrawRx - redrawLx + CURSOR_SIZE / 2; + } + + return new Area(redrawLx, -TRACK_MARGIN, redrawW, trackHeight + 2 * TRACK_MARGIN); } @Override @@ -199,14 +251,19 @@ public void stop() { hovered = null; } + @Override + public Cursor getCursor(Display display) { + return display.getSystemCursor(SWT.CURSOR_HAND); + } + @Override public boolean click() { if ((mods & SWT.MOD1) == SWT.MOD1) { state.addSelection(Selection.Kind.Counter, - transform(track.getValue(t), d -> new CounterTrack.Values(track.getCounter().name, d))); + transform(track.getValue(id), d -> new CounterTrack.Values(track.getCounter().name, d))); } else { state.setSelection(Selection.Kind.Counter, - transform(track.getValue(t), d -> new CounterTrack.Values(track.getCounter().name, d))); + transform(track.getValue(id), d -> new CounterTrack.Values(track.getCounter().name, d))); } return true; } @@ -214,9 +271,28 @@ public boolean click() { } @Override - public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { - builder.add(Selection.Kind.Counter, transform(track.getValues(ts), - data -> new CounterTrack.Values(track.getCounter().name, data))); + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Selection.Kind.Counter, computeSelection(ts)); + } + + private ListenableFuture computeSelection(TimeSpan ts) { + return transform(track.getValues(ts), + data -> new CounterTrack.Values(track.getCounter().name, data)); + } + + private CounterTrack.Stats getStats(Repainter repainter) { + TimeSpan span = state.getHighlight(); + if (!span.equals(cachedStats.span)) { + if (span.isEmpty()) { + cachedStats = new CounterTrack.Stats(track.getCounter()); + } else { + state.thenOnUiThread(track.getStats(span), stats -> { + cachedStats = stats; + repainter.repaint(Area.FULL); + }); + } + } + return cachedStats; } private static class HoverCard { @@ -225,19 +301,24 @@ private static class HoverCard { public final double mouseX; public final String label; public final String min, max, avg; + public final String minLabel, maxLabel, avgLabel; public final double leftWidth; public final Size size; - public HoverCard(Fonts.TextMeasurer tm, CounterInfo counter, double value, double startX, - double endX, double mouseX) { + public HoverCard(Fonts.TextMeasurer tm, CounterInfo counter, CounterTrack.Stats stats, + double value, double startX, double endX, double mouseX) { this.value = value; this.startX = startX; this.endX = endX; this.mouseX = mouseX; this.label = counter.unit.format(value); - this.min = counter.unit.format(counter.min); - this.max = counter.unit.format(counter.max); - this.avg = counter.unit.format(counter.avg); + this.min = counter.unit.format(stats.min); + this.max = counter.unit.format(stats.max); + this.avg = counter.unit.format(stats.avg); + boolean isTotal = stats.isTotal(); + this.minLabel = isTotal ? "Trace Min:" : "Range Min:"; + this.maxLabel = isTotal ? "Trace Max:" : "Range Max:"; + this.avgLabel = isTotal ? "Trace Avg:" : "Range Avg:"; Size valueSize = tm.measure(Fonts.Style.Normal, label); Size minSize = tm.measure(Fonts.Style.Normal, min); @@ -246,18 +327,13 @@ public HoverCard(Fonts.TextMeasurer tm, CounterInfo counter, double value, doubl double leftLabel = Math.max( tm.measure(Fonts.Style.Normal, "Value:").w, - tm.measure(Fonts.Style.Normal, "Min:").w) + HOVER_PADDING; + tm.measure(Fonts.Style.Normal, avgLabel).w) + HOVER_PADDING; double rightLabel = Math.max( - tm.measure(Fonts.Style.Normal, "Max:").w, - tm.measure(Fonts.Style.Normal, "Avg:").w) + HOVER_PADDING; + tm.measure(Fonts.Style.Normal, minLabel).w, + tm.measure(Fonts.Style.Normal, maxLabel).w) + HOVER_PADDING; this.leftWidth = leftLabel + Math.max(valueSize.w, avgSize.w); this.size = new Size(leftWidth + HOVER_PADDING + rightLabel + Math.max(minSize.w, maxSize.w), Math.max(valueSize.h + avgSize.h, minSize.h + maxSize.h) + HOVER_PADDING); } - - public double getTooltipX() { - // If the sample is smaller than the tooltip, show the tooltip after the sample. - return ((size.w < endX - startX) ? mouseX + CURSOR_SIZE / 2 : endX) + HOVER_MARGIN; - } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java index f9b6912cc4..d64e9bcd78 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CountersSelectionView.java @@ -40,9 +40,13 @@ public CountersSelectionView(Composite parent, State state, CounterTrack.Values createTableColumn( viewer, "Time", r -> timeToString(sel.ts[(Integer)r] - state.getTraceTime().start)); - for (int i = 0; i < sel.names.length; i++) { - final int idx = i; - createTableColumn(viewer, sel.names[i], r -> String.valueOf(sel.values[idx][(Integer)r])); + for (String name : sel.values.keySet()) { + createTableColumn(viewer, name, r -> { + if (sel.ids.get(name)[(Integer)r] == -1) { + return "/"; + } + return String.valueOf(sel.values.get(name)[(Integer)r]); + }); } // Skip the last row (it represents the end of the selection range). diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CpuFrequencyPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CpuFrequencyPanel.java index 4de00cee9b..b9edfd418a 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CpuFrequencyPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CpuFrequencyPanel.java @@ -31,6 +31,9 @@ */ public class CpuFrequencyPanel extends TrackPanel { private static final double HEIGHT = 30; + private static final double CURSOR_SIZE = 5; + private static final double HOVER_PADDING = 8; + private static final double HOVER_MARGIN = 10; private final CpuFrequencyTrack track; @@ -145,26 +148,34 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double // Draw change marker. ctx.path(path -> { - path.circle(xStart, y, 3); + path.circle(xStart, y, CURSOR_SIZE / 2); ctx.fillPath(path); ctx.drawPath(path); }); // Draw the tooltip. + // Technically the cursor is always drawn at the beginning of the sample, and hence when the + // track is not in quantized view, there's no cursor drawn near to the hover point most of + // the time. Hover, to be consistent with other cases, always assume the cursor is drawn at + // the hover point. + double cardX = mouseXpos + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - (textSize.w + 2 * HOVER_PADDING)) { + cardX = mouseXpos - CURSOR_SIZE / 2 - HOVER_MARGIN - textSize.w - 2 * HOVER_PADDING; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(mouseXpos + 5, 0, textSize.w + 16, h); + ctx.fillRect(cardX, 0, textSize.w + 2 * HOVER_PADDING, h); ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoverLabel, mouseXpos + 5 + 8, (h - 2 * textSize.h) / 4); + ctx.drawText(Fonts.Style.Normal, hoverLabel, cardX + HOVER_PADDING, (h - 2 * textSize.h) / 4); if (hoveredIdle != null && hoveredIdle != -1) { String idle = "Idle: " + (hoveredIdle + 1); - ctx.drawText(Fonts.Style.Normal, idle, mouseXpos + 5 + 8, (3 * h - 2 * textSize.h) / 4); + ctx.drawText(Fonts.Style.Normal, idle, cardX + HOVER_PADDING, (3 * h - 2 * textSize.h) / 4); } } // Write the Y scale on the top left corner. Size labelSize = ctx.measure(Fonts.Style.Normal, yLabel); ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(0, 0, labelSize.w + 8, labelSize.h + 8); + ctx.fillRect(0, 0, labelSize.w + HOVER_PADDING, labelSize.h + HOVER_PADDING); ctx.setForegroundColor(colors().textMain); ctx.drawText(Fonts.Style.Normal, yLabel, 4, 4); @@ -172,8 +183,9 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } @Override - public Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - CpuFrequencyTrack.Data data = track.getData(state.toRequest(), onUiThread()); + public Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + CpuFrequencyTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null) { return Hover.NONE; } @@ -189,15 +201,58 @@ public Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods hoverLabel = String.format( "%s: %,dkHz", (data.quantized ? "Average Freq" : "Freq"), hoveredValue); - double xStart = Math.floor(state.timeToPx(hoveredTs)) - 4; - double xEnd = Math.max(hoveredTsEnd == null ? - state.timeToPx(state.getVisibleTime().end) : Math.floor(state.timeToPx(hoveredTsEnd)), - x + m.measure(Fonts.Style.Normal, hoverLabel).w + 21); + double startX = Math.floor(state.timeToPx(hoveredTs)) - 4; + double endX = hoveredTsEnd == null ? + state.timeToPx(state.getVisibleTime().end) + : Math.floor(state.timeToPx(hoveredTsEnd)); return new Hover() { @Override public Area getRedraw() { - return new Area(xStart, 0, xEnd - xStart, HEIGHT); + // The area of the sample, especially when in steady performance or idle state, can be + // larger than the hover card. Hence the redraw area needs to at least cover the whole + // sample because when we move between samples, the highlight of the previous sample + // needs to be redrawn without highlight. If the redraw area only covers the hover card + // then the area that is not intersected with the hover card will not be redrawn, and + // hence the highlight remains in that area. + + // First, calculate the default left boundary x-axis coordinate and width of the + // redrawn area. + double defaultX = x - CURSOR_SIZE / 2; + double defaultW = CURSOR_SIZE + HOVER_MARGIN + HOVER_PADDING + + m.measure(Fonts.Style.Normal, hoverLabel).w + HOVER_PADDING; + + // Assuming the hover card is drawn on the right of the hover point. + + // Determine the x-axis coordinate of the left boundary of the redrawn area. + double redrawLx = Math.min(defaultX, startX); + + // Determine the x-axis coordinate of the right boundary of the redrawn area by + // comparing the right end of the sample with the right boundary of the default + // redrawn area. + double redrawRx = Math.max(defaultX + defaultW, endX); + + // Calculate the real redrawn width. + double redrawW = redrawRx - redrawLx; + + if (defaultX >= state.getWidth() - defaultW) { + + // re-calculate the left boundary of the redrawn area by comparing the start end of + // the sample with the left boundary of the default redrawn area when the hover card + // is drawn on the left side of the hover point. + redrawLx = Math.min(startX, x + CURSOR_SIZE / 2 - defaultW); + + // re-calculate the right boundary of the redrawn area by comparing the end of the + // sample with the right boundary of the default redrawn area when the hover card is + // drawn on the left side of the hover point. + redrawRx = Math.max(x + CURSOR_SIZE / 2, endX); + + // Finally, re-calculate the redrawn width, plus radius of the cursor to avoid the + // precision issue at the right edge of the cursor. + redrawW = redrawRx - redrawLx + CURSOR_SIZE / 2; + } + + return new Area(redrawLx, 0, redrawW, HEIGHT); } @Override diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CpuPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CpuPanel.java index e5ff4030a6..12661ceec6 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CpuPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CpuPanel.java @@ -24,6 +24,7 @@ import static com.google.gapid.util.MoreFutures.transform; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; @@ -31,8 +32,8 @@ import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.CpuTrack; import com.google.gapid.perfetto.models.Selection; -import com.google.gapid.perfetto.models.Selection.CombiningBuilder; import com.google.gapid.perfetto.models.ThreadInfo; +import com.google.gapid.util.Arrays; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Cursor; @@ -88,7 +89,7 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } switch (data.kind) { - case slice: renderSlices(ctx, data, h); break; + case slice: renderSlices(ctx, data, w, h); break; case summary: renderSummary(ctx, data, w, h); break; } }); @@ -97,6 +98,9 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double private void renderSummary(RenderContext ctx, CpuTrack.Data data, double w, double h) { long tStart = data.request.range.start; int start = Math.max(0, (int)((state.getVisibleTime().start - tStart) / data.bucketSize)); + Selection selected = state.getSelection(Selection.Kind.Cpu); + List visibleSelected = Lists.newArrayList(); + gradient(track.getCpu().id).applyBase(ctx); ctx.path(path -> { path.moveTo(0, h); @@ -107,31 +111,47 @@ private void renderSummary(RenderContext ctx, CpuTrack.Data data, double w, doub path.lineTo(x, y); path.lineTo(x, nextY); y = nextY; + for (String id : Arrays.getOrDefault(data.concatedIds, i, "").split(",")) { + if (!id.isEmpty() && !selected.isEmpty() && selected.contains(Long.parseLong(id))) { + visibleSelected.add(i); + break; + } + } } path.lineTo(x, h); path.close(); ctx.fillPath(path); }); + // Draw Highlight line after the whole graph is rendered, so that the highlight is on the top. + ctx.setBackgroundColor(gradient(track.getCpu().id).highlight); + for (int index : visibleSelected) { + ctx.fillRect(state.timeToPx(tStart + index * data.bucketSize), + Math.round(h * (1 - data.utilizations[index])) - 1, + state.durationToDeltaPx(data.bucketSize), 3); + } + if (hovered != null && hovered.bucket >= start) { - double x = state.timeToPx(tStart + hovered.bucket * data.bucketSize + data.bucketSize / 2); - if (x < w) { - double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; - double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; - ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(x + HOVER_MARGIN, h - HOVER_PADDING - dy, dx, dy); - ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hovered.text, x + HOVER_MARGIN + HOVER_PADDING, h - dy); - - ctx.setForegroundColor(colors().textMain); - ctx.drawCircle(x, h * (1 - hovered.utilization), CURSOR_SIZE / 2); + double mouseX = state.timeToPx(tStart + hovered.bucket * data.bucketSize + data.bucketSize / 2); + double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; + double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; + double cardX = mouseX + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - dx) { + cardX = mouseX - CURSOR_SIZE / 2 - HOVER_MARGIN - dx; } + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(cardX, h - HOVER_PADDING - dy, dx, dy); + ctx.setForegroundColor(colors().textMain); + ctx.drawText(Fonts.Style.Normal, hovered.text, cardX + HOVER_PADDING, h - dy); + + ctx.setForegroundColor(colors().textMain); + ctx.drawCircle(mouseX, h * (1 - hovered.utilization), CURSOR_SIZE / 2); } } - private void renderSlices(RenderContext ctx, CpuTrack.Data data, double h) { + private void renderSlices(RenderContext ctx, CpuTrack.Data data, double w, double h) { TimeSpan visible = state.getVisibleTime(); - Selection selected = state.getSelection(Selection.Kind.Cpu); + Selection selected = state.getSelection(Selection.Kind.Cpu); List visibleSelected = Lists.newArrayList(); for (int i = 0; i < data.starts.length; i++) { long tStart = data.starts[i]; @@ -175,29 +195,33 @@ private void renderSlices(RenderContext ctx, CpuTrack.Data data, double h) { } if (hoveredThread != null) { + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - (hoveredWidth + 2 * HOVER_PADDING)) { + cardX = mouseXpos - HOVER_MARGIN - hoveredWidth - 2 * HOVER_PADDING; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(mouseXpos + HOVER_MARGIN, 0, hoveredWidth + 2 * HOVER_PADDING, h); + ctx.fillRect(cardX, 0, hoveredWidth + 2 * HOVER_PADDING, h); ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoveredThread.title, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, 2, (h / 2) - 4); + ctx.drawText(Fonts.Style.Normal, hoveredThread.title, cardX + HOVER_PADDING, 2, (h / 2) - 4); if (!hoveredThread.subTitle.isEmpty()) { - ctx.drawText(Fonts.Style.Normal, hoveredThread.subTitle, - mouseXpos+ HOVER_MARGIN + HOVER_PADDING, (h / 2) + 2, (h / 2) - 4); + ctx.drawText(Fonts.Style.Normal, hoveredThread.subTitle, cardX + HOVER_PADDING, + (h / 2) + 2, (h / 2) - 4); } } } @Override - public Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - CpuTrack.Data data = track.getData(state.toRequest(), onUiThread()); + public Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + CpuTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null) { return Hover.NONE; } switch (data.kind) { case slice: return sliceHover(data, m, x, mods); - case summary: return summaryHover(data, m, x); + case summary: return summaryHover(data, m, x, mods); default: return Hover.NONE; } } @@ -220,7 +244,12 @@ private Hover sliceHover(CpuTrack.Data data, Fonts.TextMeasurer m, double x, int return new Hover() { @Override public Area getRedraw() { - return new Area(x + HOVER_MARGIN, 0, hoveredWidth + 2 * HOVER_PADDING, HEIGHT); + double redrawW = HOVER_MARGIN + hoveredWidth + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, 0, redrawW, HEIGHT); } @Override @@ -251,7 +280,7 @@ public boolean click() { return Hover.NONE; } - private Hover summaryHover(CpuTrack.Data data, Fonts.TextMeasurer m, double x) { + private Hover summaryHover(CpuTrack.Data data, Fonts.TextMeasurer m, double x, int mods) { long time = state.pxToTime(x); int bucket = (int)((time - data.request.range.start) / data.bucketSize); if (bucket < 0 || bucket >= data.utilizations.length) { @@ -267,29 +296,66 @@ private Hover summaryHover(CpuTrack.Data data, Fonts.TextMeasurer m, double x) { data.request.range.start + hovered.bucket * data.bucketSize + data.bucketSize / 2); double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; double dy = height; + String ids = Arrays.getOrDefault(data.concatedIds, bucket, ""); + return new Hover() { @Override public Area getRedraw() { - return new Area(mouseX - CURSOR_SIZE, -TRACK_MARGIN, CURSOR_SIZE + HOVER_MARGIN + dx, dy); + double redrawW = CURSOR_SIZE + HOVER_MARGIN + dx; + double redrawX = mouseX - CURSOR_SIZE / 2; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = mouseX + CURSOR_SIZE / 2 - redrawW; + redrawW += CURSOR_SIZE / 2; + } + return new Area(redrawX, -TRACK_MARGIN, redrawW, dy); } @Override public void stop() { hovered = null; } + + @Override + public Cursor getCursor(Display display) { + return ids.isEmpty() ? null : display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if (ids.isEmpty()) { + return false; + } + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Cpu, transform(track.getSlices(ids), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + })); + } else { + state.clearSelectedThreads(); + state.setSelection(Selection.Kind.Cpu, transform(track.getSlices(ids), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + })); + } + return true; + } }; } @Override - public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { if (area.h / height >= SELECTION_THRESHOLD) { - builder.add(Selection.Kind.Cpu, transform(track.getSlices(ts), r -> { - r.stream().forEach(s -> state.addSelectedThread(state.getThreadInfo(s.utid))); - return new CpuTrack.SlicesBuilder(r); - })); + builder.add(Selection.Kind.Cpu, computeSelection(ts)); } } + private ListenableFuture computeSelection(TimeSpan ts) { + return transform(track.getSlices(ts), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + }); + } + private static class HoverCard { public final int bucket; public final double utilization; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CpuSliceSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/CpuSliceSelectionView.java deleted file mode 100644 index 5270de4bc2..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/views/CpuSliceSelectionView.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ -package com.google.gapid.perfetto.views; - -import static com.google.gapid.perfetto.TimeSpan.timeToString; -import static com.google.gapid.widgets.Widgets.createBoldLabel; -import static com.google.gapid.widgets.Widgets.createLabel; -import static com.google.gapid.widgets.Widgets.withLayoutData; -import static com.google.gapid.widgets.Widgets.withSpans; - -import com.google.gapid.perfetto.ThreadState; -import com.google.gapid.perfetto.models.CpuTrack; -import com.google.gapid.perfetto.models.ProcessInfo; -import com.google.gapid.perfetto.models.ThreadInfo; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; - -/** - * Displays information about a selected CPU slice. - */ -public class CpuSliceSelectionView extends Composite { - public CpuSliceSelectionView(Composite parent, State state, CpuTrack.Slice slice) { - super(parent, SWT.NONE); - setLayout(new GridLayout(2, false)); - - withLayoutData(createBoldLabel(this, "Slice:"), withSpans(new GridData(), 2, 1)); - - createLabel(this, "Time:"); - createLabel(this, timeToString(slice.time - state.getTraceTime().start)); - - createLabel(this, "Duration:"); - createLabel(this, timeToString(slice.dur)); - - ThreadInfo thread = state.getThreadInfo(slice.utid); - if (thread != null) { - ProcessInfo process = state.getProcessInfo(thread.upid); - if (process != null) { - createLabel(this, "Process:"); - createLabel(this, process.getDisplay()); - } - - createLabel(this, "Thread:"); - createLabel(this, thread.getDisplay()); - } - - createLabel(this, "State:"); - createLabel(this, ThreadState.RUNNING.label); - - createLabel(this, "CPU:"); - createLabel(this, Integer.toString(slice.cpu + 1)); - - createLabel(this, "End State:"); - createLabel(this, slice.endState.label); - - createLabel(this, "Priority:"); - createLabel(this, Integer.toString(slice.priority)); - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CpuSlicesSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/CpuSlicesSelectionView.java index 4a268a3610..4c66f77de0 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CpuSlicesSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CpuSlicesSelectionView.java @@ -15,12 +15,19 @@ */ package com.google.gapid.perfetto.views; +import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createBoldLabel; +import static com.google.gapid.widgets.Widgets.createLabel; import static com.google.gapid.widgets.Widgets.createTreeColumn; import static com.google.gapid.widgets.Widgets.createTreeViewer; import static com.google.gapid.widgets.Widgets.packColumns; +import static com.google.gapid.widgets.Widgets.withLayoutData; +import static com.google.gapid.widgets.Widgets.withSpans; +import com.google.gapid.perfetto.ThreadState; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.models.CpuTrack; +import com.google.gapid.perfetto.models.CpuTrack.ByThread; import com.google.gapid.perfetto.models.ProcessInfo; import com.google.gapid.perfetto.models.ThreadInfo; @@ -29,22 +36,70 @@ import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; /** * Displays information about a list of selected CPU slices. */ public class CpuSlicesSelectionView extends Composite { - public CpuSlicesSelectionView(Composite parent, State state, CpuTrack.Slices sel) { + public CpuSlicesSelectionView(Composite parent, State state, CpuTrack.Slices slices) { super(parent, SWT.NONE); + if (slices.getCount() == 1) { + setSingleSliceView(state, slices); + } else if (slices.getCount() > 1) { + setMultiSlicesView(state, slices); + } + } + + private void setSingleSliceView(State state, CpuTrack.Slices slice) { + setLayout(new GridLayout(2, false)); + + withLayoutData(createBoldLabel(this, "Slice:"), withSpans(new GridData(), 2, 1)); + + createLabel(this, "Time:"); + createLabel(this, timeToString(slice.times.get(0) - state.getTraceTime().start)); + + createLabel(this, "Duration:"); + createLabel(this, timeToString(slice.durs.get(0))); + + ThreadInfo thread = state.getThreadInfo(slice.utids.get(0)); + if (thread != null) { + ProcessInfo process = state.getProcessInfo(thread.upid); + if (process != null) { + createLabel(this, "Process:"); + createLabel(this, process.getDisplay()); + } + + createLabel(this, "Thread:"); + createLabel(this, thread.getDisplay()); + } + + createLabel(this, "State:"); + createLabel(this, ThreadState.RUNNING.label); + + createLabel(this, "CPU:"); + createLabel(this, Integer.toString(slice.cpus.get(0) + 1)); + + createLabel(this, "End State:"); + createLabel(this, slice.endStates.get(0).label); + + createLabel(this, "Priority:"); + createLabel(this, Integer.toString(slice.priorities.get(0))); + } + + private void setMultiSlicesView(State state, CpuTrack.Slices slices) { setLayout(new FillLayout()); + CpuTrack.ByProcess[] byProcesses = CpuTrack.organizeSlicesByProcess(slices); + TreeViewer viewer = createTreeViewer(this, SWT.NONE); viewer.getTree().setHeaderVisible(true); viewer.setContentProvider(new ITreeContentProvider() { @Override public Object[] getElements(Object inputElement) { - return sel.processes.toArray(); + return byProcesses; } @Override @@ -63,7 +118,7 @@ public Object[] getChildren(Object element) { if (element instanceof CpuTrack.ByProcess) { return ((CpuTrack.ByProcess)element).threads.toArray(); } else if (element instanceof CpuTrack.ByThread) { - return ((CpuTrack.ByThread)element).slices.toArray(); + return ((ByThread)element).sliceIndexes.toArray(); } return null; } @@ -80,7 +135,7 @@ public Object[] getChildren(Object element) { ThreadInfo ti = state.getThreadInfo(tid); return (ti == null) ? " [" + tid + "]" : ti.getDisplay(); } else { - return "Slice " + ((CpuTrack.Slice)el).id; + return "Slice " + slices.ids.get((int)el); } }); createTreeColumn(viewer, "Slice Duration", el -> { @@ -89,31 +144,31 @@ public Object[] getChildren(Object element) { } else if (el instanceof CpuTrack.ByThread) { return TimeSpan.timeToString(((CpuTrack.ByThread)el).dur); } else { - return TimeSpan.timeToString(((CpuTrack.Slice)el).dur); + return TimeSpan.timeToString(slices.durs.get((int)el)); } }); createTreeColumn(viewer, "Slice Start Time", el -> { - if (el instanceof CpuTrack.Slice) { - return TimeSpan.timeToString(((CpuTrack.Slice)el).time - state.getTraceTime().start); + if (el instanceof Integer) { + return TimeSpan.timeToString(slices.times.get((int)el) - state.getTraceTime().start); } else { return ""; } }); createTreeColumn(viewer, "End State", el -> { - if (el instanceof CpuTrack.Slice) { - return ((CpuTrack.Slice)el).endState.label; + if (el instanceof Integer) { + return slices.endStates.get((int)el).label; } else { return ""; } }); createTreeColumn(viewer, "Priority", el -> { - if (el instanceof CpuTrack.Slice) { - return String.valueOf(((CpuTrack.Slice)el).priority); + if (el instanceof Integer) { + return String.valueOf(slices.priorities.get((int)el)); } else { return ""; } }); - viewer.setInput(sel); + viewer.setInput(slices); packColumns(viewer.getTree()); } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/CpuSummaryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/CpuSummaryPanel.java index a5e999fb1d..7f48166d58 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/CpuSummaryPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/CpuSummaryPanel.java @@ -23,13 +23,14 @@ import static com.google.gapid.perfetto.views.StyleConstants.mainGradient; import static com.google.gapid.util.MoreFutures.transform; +import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.CpuSummaryTrack; -import com.google.gapid.perfetto.models.CpuTrack; +import com.google.gapid.perfetto.models.CpuTrack.Slices; import com.google.gapid.perfetto.models.Selection; /** @@ -101,25 +102,28 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double }); if (hovered != null && hovered.bucket >= start) { - double x = state.timeToPx(tStart + hovered.bucket * data.bucketSize + data.bucketSize / 2); - if (x < w) { - double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; - double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; - ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(x + HOVER_MARGIN, h - HOVER_PADDING - dy, dx, dy); - ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hovered.text, x + HOVER_MARGIN + HOVER_PADDING, h - dy); - - ctx.setForegroundColor(colors().textMain); - ctx.drawCircle(x, h * (1 - hovered.utilization), CURSOR_SIZE / 2); + double mouseX = state.timeToPx(tStart + hovered.bucket * data.bucketSize + data.bucketSize / 2); + double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; + double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; + double cardX = mouseX + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - dx) { + cardX = mouseX - CURSOR_SIZE / 2 - HOVER_MARGIN - dx; } + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(cardX, h - HOVER_PADDING - dy, dx, dy); + ctx.setForegroundColor(colors().textMain); + ctx.drawText(Fonts.Style.Normal, hovered.text, cardX + HOVER_PADDING, h - dy); + + ctx.setForegroundColor(colors().textMain); + ctx.drawCircle(mouseX, h * (1 - hovered.utilization), CURSOR_SIZE / 2); } }); } @Override - protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - CpuSummaryTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + CpuSummaryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null || data.utilizations.length == 0) { return Hover.NONE; } @@ -142,7 +146,16 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m return new Hover() { @Override public Area getRedraw() { - return new Area(mouseX - CURSOR_SIZE, -TRACK_MARGIN, CURSOR_SIZE + HOVER_MARGIN + dx, dy); + double redrawW = CURSOR_SIZE + HOVER_MARGIN + dx; + double redrawX = mouseX - CURSOR_SIZE / 2; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = mouseX + CURSOR_SIZE / 2 - redrawW; + // If the hover card is drawn on the left side of the hover point, when moving the mouse + // from left to right, the right edge of the cursor doesn't seem to get redrawn all the + // time, this looks like a precision issue. Plus the radius of cursor here to avoid it. + redrawW += CURSOR_SIZE / 2; + } + return new Area(redrawX, -TRACK_MARGIN, redrawW, dy); } @Override @@ -155,13 +168,17 @@ public void stop() { @Override public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { if (area.h / height >= SELECTION_THRESHOLD) { - builder.add(Selection.Kind.Cpu, transform(track.getSlices(ts), r -> { - r.stream().forEach(s -> state.addSelectedThread(state.getThreadInfo(s.utid))); - return new CpuTrack.SlicesBuilder(r); - })); + builder.add(Selection.Kind.Cpu, computeSelection(ts)); } } + private ListenableFuture computeSelection(TimeSpan ts) { + return transform(track.getSlices(ts), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + }); + } + private static class HoverCard { public final int bucket; public final double utilization; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/EnergyBreakdownPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/EnergyBreakdownPanel.java new file mode 100644 index 0000000000..decfa84a6a --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/EnergyBreakdownPanel.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.views.Loading.drawLoading; +import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; +import static com.google.gapid.perfetto.views.StyleConstants.colors; +import static com.google.gapid.perfetto.views.StyleConstants.mainGradient; +import static com.google.gapid.util.MoreFutures.transform; + +import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.Fonts; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.models.CounterInfo; +import com.google.gapid.perfetto.models.EnergyBreakdownTrack; +import com.google.gapid.perfetto.models.Selection; +import java.util.List; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.widgets.Display; + +public class EnergyBreakdownPanel extends TrackPanel implements Selectable { + private static final double HOVER_MARGIN = 10; + private static final double HOVER_PADDING = 4; + private static final double CURSOR_SIZE = 5; + + protected final EnergyBreakdownTrack track; + protected final double trackHeight; + protected HoverCard hovered = null; + private EnergyBreakdownTrack.Stats cachedStats; + + public EnergyBreakdownPanel(State state, EnergyBreakdownTrack track, double trackHeight) { + super(state); + this.track = track; + this.trackHeight = trackHeight; + this.cachedStats = new EnergyBreakdownTrack.Stats(track.getCounter()); + } + + @Override + public EnergyBreakdownPanel copy() { + return new EnergyBreakdownPanel(state, track, trackHeight); + } + + @Override + public String getTitle() { + return track.getCounter().name; + } + + @Override + public String getTooltip() { + CounterInfo counter = track.getCounter(); + StringBuilder sb = new StringBuilder().append("\\b").append(counter.name); + if (!counter.description.isEmpty()) { + sb.append("\n").append(counter.description); + } + return sb.toString(); + } + + @Override + public double getHeight() { + return trackHeight; + } + + @Override + protected void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { + ctx.trace( + "Energy Counter", + () -> { + EnergyBreakdownTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + drawLoading(ctx, data, state, h); + + if (data == null || data.ts.length == 0) { + return; + } + + CounterInfo counter = track.getCounter(); + + double min = counter.range.min; + double range = counter.range.range(); + + Selection selected = state.getSelection(Selection.Kind.Counter); + List visibleSelected = Lists.newArrayList(); + mainGradient().applyBaseAndBorder(ctx); + ctx.path( + path -> { + double lastX = state.timeToPx(data.ts[0]), lastY = h; + path.moveTo(lastX, lastY); + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = (trackHeight - 1) * (1 - (data.values[i] - min) / range); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + if (selected.contains(data.ids[i])) { + visibleSelected.add(i); + } + } + path.lineTo(lastX, h); + ctx.fillPath(path); + ctx.drawPath(path); + }); + + // Draw highlight line after the whole graph is rendered, so that the highlight is on the + // top. + ctx.setBackgroundColor(mainGradient().highlight); + for (int index : visibleSelected) { + double startX = state.timeToPx(data.ts[index]); + double endX = + (index >= data.ts.length - 1) ? startX : state.timeToPx(data.ts[index + 1]); + double y = (trackHeight - 1) * (1 - (data.values[index] - min) / range); + ctx.fillRect(startX, y - 1, endX - startX, 3); + } + + if (hovered != null) { + double y = (trackHeight - 1) * (1 - (hovered.value - min) / range); + ctx.setBackgroundColor(mainGradient().highlight); + ctx.fillRect(hovered.startX, y - 1, hovered.endX - hovered.startX, trackHeight - y + 1); + ctx.setForegroundColor(colors().textMain); + ctx.drawCircle(hovered.mouseX, y, CURSOR_SIZE / 2); + + ctx.setBackgroundColor(colors().hoverBackground); + double bgH = Math.max(hovered.size.h, trackHeight); + // The left x-axis coordinate of HoverCard. + double hx = hovered.mouseX + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (hx >= w - (2 * HOVER_PADDING + hovered.size.w)) { + hx = + hovered.mouseX + - CURSOR_SIZE / 2 + - HOVER_MARGIN + - 2 * HOVER_PADDING + - hovered.size.w; + } + ctx.fillRect( + hx, Math.min((trackHeight - bgH) / 2, 0), 2 * HOVER_PADDING + hovered.size.w, bgH); + ctx.setForegroundColor(colors().textMain); + // The left x-axis coordinate of the label. + double x = hx + HOVER_PADDING; + y = (trackHeight - hovered.size.h) / 2; + ctx.drawText(Fonts.Style.Normal, hovered.text, x, y); + } + }); + } + + @Override + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + EnergyBreakdownTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + if (data == null || data.ts.length == 0) { + return Hover.NONE; + } + + long time = state.pxToTime(x); + if (time < data.ts[0] || time > data.ts[data.ts.length - 1]) { + return Hover.NONE; + } + + int idx = 0; + for (; idx < data.ts.length - 1; idx++) { + if (data.ts[idx + 1] > time) { + break; + } + } + + if (idx >= data.ts.length) { + return Hover.NONE; + } + + long id = data.ids[idx]; + double startX = state.timeToPx(data.ts[idx]); + double endX = (idx >= data.ts.length - 1) ? startX : state.timeToPx(data.ts[idx + 1]); + hovered = new HoverCard(m, track.getCounter(), data.values[idx], startX, endX, x); + + return new Hover() { + @Override + public Area getRedraw() { + // The area of the sample can be larger than the hover card. Hence the redraw area needs to + // at least cover the whole sample because when we move between samples, the highlight of + // the previous sample needs to be redrawn without highlight. If the redraw area only + // covers the hover card then the area that is not intersected with the hover card will + // not be redrawn, and hence the highlight remains in that area. + + // First, calculate the default left boundary x-axis coordinate and width of the + // redrawn area. + final double defaultX = hovered.mouseX - CURSOR_SIZE / 2; + final double defaultW = + CURSOR_SIZE + HOVER_MARGIN + HOVER_PADDING + hovered.size.w + HOVER_PADDING; + + // Assuming the hover card is drawn on the right of the hover point. + + // Determine the x-axis coordinate of the left boundary of the redrawn area. + double redrawLx = Math.min(defaultX, startX); + + // Determine the x-axis coordinate of the right boundary of the redrawn area by comparing + // the right end of the sample with the right boundary of the default redrawn area. + double redrawRx = Math.max(defaultX + defaultW, endX); + + // Calculate the real redrawn width. + double redrawW = redrawRx - redrawLx; + + if (defaultX >= state.getWidth() - defaultW) { + + // re-calculate the left boundary of the redrawn area by comparing the start end of the + // sample with the left boundary of the default redrawn area when the hover card is drawn + // on the left side of the hover point. + redrawLx = Math.min(startX, hovered.mouseX + CURSOR_SIZE / 2 - defaultW); + + // re-calculate the right boundary of the redrawn area by comparing the end of the sample + // with the right boundary of the default redrawn area when the hover card is drawn on + // the left side of the hover point. + redrawRx = Math.max(hovered.mouseX + CURSOR_SIZE / 2, endX); + + // Finally, re-calculate the redrawn width, plus radius of the cursor to avoid the + // precision issue at the right edge of the cursor. + redrawW = redrawRx - redrawLx + CURSOR_SIZE / 2; + } + + return new Area(redrawLx, -TRACK_MARGIN, redrawW, trackHeight + 2 * TRACK_MARGIN); + } + + @Override + public void stop() { + hovered = null; + } + + @Override + public Cursor getCursor(Display display) { + return display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection( + Selection.Kind.Counter, + transform( + track.getValue(id, track.getCounter().id), + d -> new EnergyBreakdownTrack.Values(track.getCounter().name, d))); + } else { + state.setSelection( + Selection.Kind.Counter, + transform( + track.getValue(id, track.getCounter().id), + d -> new EnergyBreakdownTrack.Values(track.getCounter().name, d))); + } + return true; + } + }; + } + + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Selection.Kind.Counter, computeSelection(ts)); + } + + private ListenableFuture computeSelection(TimeSpan ts) { + return transform( + track.getValues(ts), + data -> new EnergyBreakdownTrack.Values(track.getCounter().name, data)); + } + + private static class HoverCard { + public final double value; + public final double startX, endX; + public final double mouseX; + public final double width; + public final Size size; + public final String text; + + public HoverCard( + Fonts.TextMeasurer tm, + CounterInfo counter, + double value, + double startX, + double endX, + double mouseX) { + this.value = value; + this.startX = startX; + this.endX = endX; + this.mouseX = mouseX; + text = counter.unit.format(value) + " uws"; + Size valueSize = tm.measure(Fonts.Style.Normal, text); + this.width = valueSize.w; + this.size = new Size(width + HOVER_PADDING, valueSize.h + HOVER_PADDING); + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/EnergyBreakdownSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/EnergyBreakdownSelectionView.java new file mode 100644 index 0000000000..cbccafbaa3 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/EnergyBreakdownSelectionView.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createTableColumn; +import static com.google.gapid.widgets.Widgets.createTableViewer; +import static com.google.gapid.widgets.Widgets.packColumns; + +import com.google.gapid.perfetto.models.EnergyBreakdownTrack; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +public class EnergyBreakdownSelectionView extends Composite { + public EnergyBreakdownSelectionView( + Composite parent, State state, EnergyBreakdownTrack.Values sel) { + super(parent, SWT.None); + setLayout(new FillLayout()); + + TableViewer viewer = createTableViewer(this, SWT.NONE); + viewer.setContentProvider(new ArrayContentProvider()); + viewer.setLabelProvider(new LabelProvider()); + + createTableColumn( + viewer, "Time", r -> timeToString(sel.ts[(Integer) r] - state.getTraceTime().start)); + + for (String name : sel.values.keySet()) { + createTableColumn( + viewer, + name + " (Total energy in uws)", + r -> { + if (sel.ids.get(name)[(Integer) r] == -1) { + return "/"; + } + return String.valueOf(sel.values.get(name)[(Integer) r]); + }); + } + + for (Long uid : sel.uidValues.keySet()) { + createTableColumn( + viewer, + "UID: " + String.valueOf(uid) + " (uws)", + r -> { + if (sel.uidValues.get(uid) == null) { + return ""; + } + return String.valueOf(sel.uidValues.get(uid)[(Integer) r]); + }); + } + + Integer[] rows = new Integer[sel.ts.length - 1 >= 0 ? sel.ts.length - 1 : 0]; + for (int i = 0; i < rows.length; i++) { + rows[i] = i; + } + viewer.setInput(rows); + packColumns(viewer.getTable()); + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/FilterablePanel.java b/gapic/src/main/com/google/gapid/perfetto/views/FilterablePanel.java new file mode 100644 index 0000000000..4dd61e6e32 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/FilterablePanel.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +/** + * Panels implementing this interface may be filtered based on a user search. + */ +public interface FilterablePanel { + public boolean include(String search); +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsMultiSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsMultiSelectionView.java deleted file mode 100644 index 470a57051b..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsMultiSelectionView.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ -package com.google.gapid.perfetto.views; - -import static com.google.gapid.perfetto.TimeSpan.timeToString; -import static com.google.gapid.widgets.Widgets.createTreeColumn; -import static com.google.gapid.widgets.Widgets.createTreeViewer; -import static com.google.gapid.widgets.Widgets.packColumns; - -import com.google.gapid.perfetto.models.FrameEventsTrack; - -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; - -/** - * Displays information about a list of selected slices. - */ -public class FrameEventsMultiSelectionView extends Composite { - public FrameEventsMultiSelectionView(Composite parent, FrameEventsTrack.Slices sel) { - super(parent, SWT.NONE); - setLayout(new FillLayout()); - - TreeViewer viewer = createTreeViewer(this, SWT.NONE); - viewer.getTree().setHeaderVisible(true); - viewer.setContentProvider(new ITreeContentProvider() { - @Override - public Object[] getElements(Object inputElement) { - return sel.nodes.toArray(); - } - - @Override - public boolean hasChildren(Object element) { - return false; - } - - @Override - public Object getParent(Object element) { - return null; - } - - @Override - public Object[] getChildren(Object element) { - return null; - } - }); - viewer.setLabelProvider(new LabelProvider()); - - createTreeColumn(viewer, "Name", e -> n(e).name); - createTreeColumn(viewer, "TrackId", e -> Long.toString(n(e).trackId)); - createTreeColumn(viewer, "Self Time", e -> timeToString(n(e).self)); - viewer.setInput(sel); - packColumns(viewer.getTree()); - } - - protected static FrameEventsTrack.Node n(Object o) { - return (FrameEventsTrack.Node)o; - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsPanel.java new file mode 100644 index 0000000000..ccd9166b0d --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsPanel.java @@ -0,0 +1,320 @@ +/* + * Copyright (C) 2019 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.views.Loading.drawLoading; +import static com.google.gapid.perfetto.views.StyleConstants.SELECTION_THRESHOLD; +import static com.google.gapid.perfetto.views.StyleConstants.colors; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.Fonts; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.models.ArgSet; +import com.google.gapid.perfetto.models.FrameEventsTrack; +import com.google.gapid.perfetto.models.FrameInfo; +import com.google.gapid.perfetto.models.Selection; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.graphics.RGBA; +import org.eclipse.swt.widgets.Display; + +import java.util.List; +import java.util.Set; + +/** + * Draws the instant events and the Displayed Frame track for the Surface Flinger Frame Events + */ +public class FrameEventsPanel extends TrackPanel + implements Selectable { + private static final double SLICE_HEIGHT = 30; + private static final double HOVER_MARGIN = 10; + private static final double HOVER_PADDING = 4; + private static final int BOUNDING_BOX_LINE_WIDTH = 3; + + private final FrameInfo.Event event; + protected final FrameEventsTrack track; + + protected double mouseXpos, mouseYpos; + protected String hoveredTitle; + protected String hoveredCategory; + protected Size hoveredSize = Size.ZERO; + + public FrameEventsPanel(State state, FrameInfo.Event event, FrameEventsTrack track) { + super(state); + this.event = event; + this.track = track; + } + + @Override + public String getTitle() { + return event.getDisplay(); + } + + @Override + public String getTooltip() { + return "\\b" + event.tooltip; + } + + @Override + public double getHeight() { + return event.maxDepth * SLICE_HEIGHT; + } + + @Override + public void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { + ctx.trace("FrameEvents", () -> { + FrameEventsTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + drawLoading(ctx, data, state, h); + + if (data == null) { + return; + } + renderSlices(ctx, data, w); + }); + } + + public void renderSlices(RenderContext ctx, FrameEventsTrack.Data data, double w) { + TimeSpan visible = state.getVisibleTime(); + Selection selected = state.getSelection(Selection.Kind.FrameEvents); + List visibleSelected = Lists.newArrayList(); + + Set selectedFrameNumbers = getSelectedFrameNumbers(state); + + for (int i = 0; i < data.starts.length; i++) { + long tStart = data.starts[i]; + long tEnd = data.ends[i]; + int depth = data.depths[i]; + String title = buildSliceTitle(data.titles[i], data.args[i]); + + if (tEnd <= visible.start || tStart >= visible.end) { + continue; + } + double rectStart = state.timeToPx(tStart); + StyleConstants.Gradient color = getSliceColor(data.titles[i]); + if (!selectedFrameNumbers.isEmpty() && !selectedFrameNumbers.contains(data.frameNumbers[i])) { + ctx.setBackgroundColor(color.disabled); + } else { + color.applyBase(ctx); + } + + if (tEnd - tStart > 0 ) { // Slice + double rectWidth = Math.max(1, state.timeToPx(tEnd) - rectStart); + double y = depth * SLICE_HEIGHT; + ctx.fillRect(rectStart, y, rectWidth, SLICE_HEIGHT); + + if (selected.contains(data.ids[i]) || selectedFrameNumbers.contains(data.frameNumbers[i])) { + visibleSelected.add(Highlight.slice(color.border, rectStart, y, rectWidth)); + } + + // Don't render text when we have less than 7px to play with. + if (rectWidth < 7) { + continue; + } + + ctx.setForegroundColor(colors().textMain); + ctx.drawText( + Fonts.Style.Normal, title, rectStart + 2, y + 2, rectWidth - 4, SLICE_HEIGHT - 4); + } else { // Instant event (diamond) + double rectWidth = 20; + double y = depth * SLICE_HEIGHT; + double[] diamondX = { rectStart - (rectWidth / 2), rectStart, rectStart + (rectWidth / 2), + rectStart}; + double[] diamondY = { y + (SLICE_HEIGHT / 2), y, y + (SLICE_HEIGHT / 2), y + SLICE_HEIGHT }; + ctx.fillPolygon(diamondX, diamondY, 4); + + if (selected.contains(data.ids[i]) || selectedFrameNumbers.contains(data.frameNumbers[i])) { + visibleSelected.add(Highlight.diamond(color.highlight, diamondX, diamondY)); + } + + ctx.setForegroundColor(colors().textMain); + ctx.drawText( + Fonts.Style.Normal, title.substring(0,1), rectStart - rectWidth/2 + 1, y + 1, + rectWidth - 1, SLICE_HEIGHT - 4); + } + } + + for (Highlight highlight : visibleSelected) { + highlight.draw(ctx); + } + + if (hoveredTitle != null) { + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - (hoveredSize.w + 2 * HOVER_PADDING)) { + cardX = mouseXpos - HOVER_MARGIN - hoveredSize.w - 2 * HOVER_PADDING; + } + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(cardX, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + + ctx.setForegroundColor(colors().textMain); + ctx.drawText(Fonts.Style.Normal, hoveredTitle, cardX + HOVER_PADDING, + mouseYpos + HOVER_PADDING / 2); + if (!hoveredCategory.isEmpty()) { + ctx.setForegroundColor(colors().textAlt); + ctx.drawText(Fonts.Style.Normal, hoveredCategory, cardX + HOVER_PADDING, + mouseYpos + hoveredSize.h / 2, hoveredSize.h / 2); + } + } + } + + private static String buildSliceTitle(String title, ArgSet args) { + Object w = args.get("width"), h = args.get("height"); + return (w == null || h == null) ? title : title + " (" + w + "x" + h + ")"; + } + + @Override + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + FrameEventsTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + if (data == null) { + return Hover.NONE; + } + return sliceHover(data, m, x, y, mods); + } + + private Hover sliceHover(FrameEventsTrack.Data data, Fonts.TextMeasurer m, double x, double y, int mods) { + int depth = (int)(y / SLICE_HEIGHT); + if (depth < 0 || depth > event.maxDepth) { + return Hover.NONE; + } + + mouseXpos = x; + mouseYpos = depth * SLICE_HEIGHT; + for (int i = 0; i < data.starts.length; i++) { + double ts = state.timeToPx(data.starts[i]); + double endts = state.timeToPx(data.ends[i]); + if (ts == endts) { + endts = ts + 12; + ts -= 12; + } + if (data.depths[i] == depth && x >= ts && x<= endts) { + hoveredTitle = data.titles[i]; + hoveredCategory = ""; + if (hoveredTitle.isEmpty()) { + if (hoveredCategory.isEmpty()) { + return Hover.NONE; + } + hoveredCategory = ""; + } + hoveredTitle = buildSliceTitle(hoveredTitle, data.args[i]); + + hoveredSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, + m.measure(Fonts.Style.Normal, hoveredTitle), + hoveredCategory.isEmpty() ? Size.ZERO : m.measure(Fonts.Style.Normal, hoveredCategory)); + mouseYpos = Math.max(0, Math.min(mouseYpos - (hoveredSize.h - SLICE_HEIGHT) / 2, + (1 + event.maxDepth) * SLICE_HEIGHT - hoveredSize.h)); + long id = data.ids[i]; + return new Hover() { + @Override + public Area getRedraw() { + double redrawW = HOVER_MARGIN + hoveredSize.w + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, mouseYpos, redrawW, hoveredSize.h); + } + + @Override + public void stop() { + hoveredTitle = hoveredCategory = null; + } + + @Override + public Cursor getCursor(Display display) { + return (id < 0) ? null : display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if (id < 0) { + return false; + } + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.FrameEvents, track.getSlice(id)); + } else { + state.setSelection(Selection.Kind.FrameEvents, track.getSlice(id)); + } + return true; + } + }; + } + } + return Hover.NONE; + } + + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + int startDepth = (int)(area.y / SLICE_HEIGHT); + int endDepth = (int)((area.y + area.h) / SLICE_HEIGHT); + if (startDepth == endDepth && area.h / SLICE_HEIGHT < SELECTION_THRESHOLD) { + return; + } + if (((startDepth + 1) * SLICE_HEIGHT - area.y) / SLICE_HEIGHT < SELECTION_THRESHOLD) { + startDepth++; + } + if ((area.y + area.h - endDepth * SLICE_HEIGHT) / SLICE_HEIGHT < SELECTION_THRESHOLD) { + endDepth--; + } + if (startDepth > endDepth) { + return; + } + + if (endDepth >= 0) { + if (endDepth >= event.maxDepth) { + endDepth = Integer.MAX_VALUE; + } + + builder.add(Selection.Kind.FrameEvents, track.getSlices(ts, startDepth, endDepth)); + } + } + + private static Set getSelectedFrameNumbers(State state) { + Selection selection = state.getSelection(Selection.Kind.FrameEvents); + Set selected = Sets.newHashSet(); + if (selection instanceof FrameEventsTrack.Slices) { + selected = ((FrameEventsTrack.Slices)selection).getSelectedFrameNumbers(); + } + return selected; + } + + @Override + public FrameEventsPanel copy() { + return new FrameEventsPanel(state, event, track); + } + + private interface Highlight { + public void draw(RenderContext ctx); + + public static Highlight slice(RGBA color, double x, double y, double w) { + return ctx -> { + ctx.setForegroundColor(color); + ctx.drawRect(x, y, w, SLICE_HEIGHT, BOUNDING_BOX_LINE_WIDTH); + }; + } + + public static Highlight diamond(RGBA color, double[] xs, double[] ys) { + return ctx -> { + ctx.setForegroundColor(color); + ctx.drawPolygon(xs, ys, 4); + }; + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSelectionView.java index 2660d661b1..83de3cd990 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSelectionView.java @@ -19,6 +19,10 @@ import static com.google.gapid.widgets.Widgets.createBoldLabel; import static com.google.gapid.widgets.Widgets.createComposite; import static com.google.gapid.widgets.Widgets.createLabel; +import static com.google.gapid.widgets.Widgets.createSelectableLabel; +import static com.google.gapid.widgets.Widgets.createTreeColumn; +import static com.google.gapid.widgets.Widgets.createTreeViewer; +import static com.google.gapid.widgets.Widgets.packColumns; import static com.google.gapid.widgets.Widgets.withIndents; import static com.google.gapid.widgets.Widgets.withLayoutData; import static com.google.gapid.widgets.Widgets.withMargin; @@ -27,7 +31,11 @@ import com.google.common.collect.Iterables; import com.google.gapid.perfetto.models.FrameEventsTrack; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; @@ -39,25 +47,51 @@ public class FrameEventsSelectionView extends Composite { private static final int PROPERTIES_PER_PANEL = 8; private static final int PANEL_INDENT = 25; - public FrameEventsSelectionView(Composite parent, State state, FrameEventsTrack.Slice slice) { + public FrameEventsSelectionView(Composite parent, State state, FrameEventsTrack.Slices slices) { super(parent, SWT.NONE); - setLayout(withMargin(new GridLayout(2, false), 0, 0)); + if (slices.getCount() == 1) { + setSingleSliceView(state, slices); + } else if (slices.getCount() > 1) { + setMultiSlicesView(slices); + } + } + + private void setSingleSliceView(State state, FrameEventsTrack.Slices slice) { + setLayout(withMargin(new GridLayout(3, false), 0, 0)); Composite main = withLayoutData(createComposite(this, new GridLayout(2, false)), new GridData(SWT.LEFT, SWT.TOP, false, false)); withLayoutData(createBoldLabel(main, "Slice:"), withSpans(new GridData(), 2, 1)); createLabel(main, "Name:"); - createLabel(main, slice.name); + createSelectableLabel(main, slice.names.get(0)); createLabel(main, "Time:"); - createLabel(main, timeToString(slice.time - state.getTraceTime().start)); + createSelectableLabel(main, timeToString(slice.times.get(0) - state.getTraceTime().start)); createLabel(main, "Duration:"); - createLabel(main, timeToString(slice.dur)); + createSelectableLabel(main, timeToString(slice.durs.get(0))); + + // If the selected event is a displayed frame slice, show the frame stats + Composite stats = withLayoutData(createComposite(this, new GridLayout(2, false)), + withIndents(new GridData(SWT.LEFT, SWT.TOP, false, false), PANEL_INDENT, 0)); + withLayoutData(createBoldLabel(stats, "Frame Stats:"), + withSpans(new GridData(), 2, 1)); + + createLabel(stats, "Frame number: "); + createSelectableLabel(stats, Long.toString(slice.frameNumbers.get(0))); + + createLabel(stats, "Queue to Acquire: "); + createSelectableLabel(stats, timeToString(slice.queueToAcquireTimes.get(0))); - if (!slice.args.isEmpty()) { - String[] keys = Iterables.toArray(slice.args.keys(), String.class); + createLabel(stats, "Acquire to Latch: "); + createSelectableLabel(stats, timeToString(slice.acquireToLatchTimes.get(0))); + + createLabel(stats, "Latch to Present: "); + createSelectableLabel(stats, timeToString(slice.latchToPresentTimes.get(0))); + + if (!slice.argsets.get(0).isEmpty()) { + String[] keys = Iterables.toArray(slice.argsets.get(0).keys(), String.class); int panels = (keys.length + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; Composite props = withLayoutData(createComposite(this, new GridLayout(2 * panels, false)), withIndents(new GridData(SWT.LEFT, SWT.TOP, false, false), PANEL_INDENT, 0)); @@ -67,9 +101,9 @@ public FrameEventsSelectionView(Composite parent, State state, FrameEventsTrack. for (int i = 0; i < keys.length && i < PROPERTIES_PER_PANEL; i++) { int cols = (keys.length - i + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; for (int c = 0; c < cols; c++) { - withLayoutData(createLabel(props, keys[i + c * PROPERTIES_PER_PANEL] + ":"), + withLayoutData(createSelectableLabel(props, keys[i + c * PROPERTIES_PER_PANEL] + ":"), withIndents(new GridData(), (c == 0) ? 0 : PANEL_INDENT, 0)); - createLabel(props, String.valueOf(slice.args.get(keys[i + c * PROPERTIES_PER_PANEL]))); + createSelectableLabel(props, String.valueOf(slice.argsets.get(0).get(keys[i + c * PROPERTIES_PER_PANEL]))); } if (cols != panels) { withLayoutData(createLabel(props, ""), withSpans(new GridData(), 2 * (panels - cols), 1)); @@ -77,4 +111,50 @@ public FrameEventsSelectionView(Composite parent, State state, FrameEventsTrack. } } } + + private void setMultiSlicesView(FrameEventsTrack.Slices slices) { + setLayout(new FillLayout()); + + FrameEventsTrack.Node[] nodes = FrameEventsTrack.organizeSlicesToNodes(slices); + + TreeViewer viewer = createTreeViewer(this, SWT.NONE); + viewer.getTree().setHeaderVisible(true); + viewer.setContentProvider(new ITreeContentProvider() { + @Override + public Object[] getElements(Object inputElement) { + return nodes; + } + + @Override + public boolean hasChildren(Object element) { + return false; + } + + @Override + public Object getParent(Object element) { + return null; + } + + @Override + public Object[] getChildren(Object element) { + return null; + } + }); + viewer.setLabelProvider(new LabelProvider()); + + createTreeColumn(viewer, "Name", e -> n(e).name); + createTreeColumn(viewer, "Frame Number", e -> Long.toString(n(e).frameNumber)); + createTreeColumn(viewer, "Self Time", e -> timeToString(n(e).self)); + createTreeColumn(viewer, "Event type", e -> n(e).eventName); + createTreeColumn(viewer, "Layers", e -> n(e).layerName); + createTreeColumn(viewer, "Queue to Acquire Time", e -> timeToString(n(e).queueToAcquireTime)); + createTreeColumn(viewer, "Acquire to Latch Time", e -> timeToString(n(e).acquireToLatchTime)); + createTreeColumn(viewer, "Latch to Present Time", e -> timeToString(n(e).latchToPresentTime)); + viewer.setInput(slices); + packColumns(viewer.getTree()); + } + + private static FrameEventsTrack.Node n(Object o) { + return (FrameEventsTrack.Node)o; + } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSummaryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSummaryPanel.java deleted file mode 100644 index e7ba88e63c..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/views/FrameEventsSummaryPanel.java +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ -package com.google.gapid.perfetto.views; - -import static com.google.gapid.perfetto.views.Loading.drawLoading; -import static com.google.gapid.perfetto.views.StyleConstants.SELECTION_THRESHOLD; -import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; -import static com.google.gapid.perfetto.views.StyleConstants.colors; -import static com.google.gapid.perfetto.views.StyleConstants.mainGradient; -import static com.google.gapid.util.MoreFutures.transform; - -import com.google.common.collect.Lists; -import com.google.gapid.perfetto.TimeSpan; -import com.google.gapid.perfetto.canvas.Area; -import com.google.gapid.perfetto.canvas.Fonts; -import com.google.gapid.perfetto.canvas.RenderContext; -import com.google.gapid.perfetto.canvas.Size; -import com.google.gapid.perfetto.models.ArgSet; -import com.google.gapid.perfetto.models.FrameEventsTrack; -import com.google.gapid.perfetto.models.FrameEventsTrack.Slice; -import com.google.gapid.perfetto.models.GpuInfo; -import com.google.gapid.perfetto.models.Selection; -import com.google.gapid.perfetto.models.Selection.CombiningBuilder; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Cursor; -import org.eclipse.swt.graphics.RGBA; -import org.eclipse.swt.widgets.Display; - -import java.util.List; - -/** - * Draws the instant events and the Displayed Frame track for the Surface Flinger Frame Events - */ -public class FrameEventsSummaryPanel extends TrackPanel - implements Selectable { - private static final double SLICE_HEIGHT = 30; - private static final double HOVER_MARGIN = 10; - private static final double HOVER_PADDING = 4; - private static final double CURSOR_SIZE = 5; - private static final int BOUNDING_BOX_LINE_WIDTH = 3; - - private final GpuInfo.Buffer buffer; - protected final FrameEventsTrack track; - - protected double mouseXpos, mouseYpos; - protected String hoveredTitle; - protected String hoveredCategory; - protected Size hoveredSize = Size.ZERO; - protected HoverCard hovered; - - public FrameEventsSummaryPanel(State state, GpuInfo.Buffer buffer, FrameEventsTrack track) { - super(state); - this.buffer = buffer; - this.track = track; - } - - - @Override - public String getTitle() { - return buffer.getDisplay(); - } - - @Override - public double getHeight() { - return buffer.maxDepth * SLICE_HEIGHT; - } - - @Override - public void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { - ctx.trace("FrameEventsSummary", () -> { - FrameEventsTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); - drawLoading(ctx, data, state, h); - - if (data == null) { - return; - } - - switch(data.kind) { - case slices: renderSlices(ctx, data); break; - case summary: renderSummary(ctx, data, w, h); break; - } - }); - } - - - private void renderSummary( - RenderContext ctx, FrameEventsTrack.Data data, double w, double h) { - long tStart = data.request.range.start; - int start = Math.max(0, (int)((state.getVisibleTime().start - tStart) / data.bucketSize)); - - mainGradient().applyBaseAndBorder(ctx); - ctx.path(path -> { - path.moveTo(0, h); - double y = h, x = 0; - for (int i = start; i < data.numEvents.length && x < w; i++) { - x = state.timeToPx(tStart + i * data.bucketSize); - double nextY = Math.round(Math.max(0,h - (h * (data.numEvents[i])))); - path.lineTo(x, y); - path.lineTo(x, nextY); - y = nextY; - } - path.lineTo(x, h); - path.close(); - ctx.fillPath(path); - ctx.drawPath(path); - }); - - if (hovered != null && hovered.bucket >= start) { - double x = state.timeToPx(tStart + hovered.bucket * data.bucketSize + data.bucketSize / 2); - if (x < w) { - double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; - double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; - ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(x + HOVER_MARGIN, h - HOVER_PADDING - dy, dx, dy); - ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hovered.text, x + HOVER_MARGIN + HOVER_PADDING, h - dy); - - ctx.setForegroundColor(colors().textMain); - ctx.drawCircle(x, Math.round(Math.max(0,h - (h * (hovered.numEvents)))), CURSOR_SIZE / 2); - } - } - } - - - public void renderSlices(RenderContext ctx, FrameEventsTrack.Data data) { - TimeSpan visible = state.getVisibleTime(); - Selection selected = state.getSelection(Selection.Kind.FrameEvents); - List visibleSelected = Lists.newArrayList(); - - for (int i = 0; i < data.starts.length; i++) { - long tStart = data.starts[i]; - long tEnd = data.ends[i]; - int depth = data.depths[i]; - String title = buildSliceTitle(data.titles[i], data.args[i]); - - if (tEnd <= visible.start || tStart >= visible.end) { - continue; - } - double rectStart = state.timeToPx(tStart); - StyleConstants.Gradient color = getSliceColor(data.titles[i]); - color.applyBase(ctx); - - if (tEnd - tStart > 3 ) { - double rectWidth = Math.max(1, state.timeToPx(tEnd) - rectStart); - double y = depth * SLICE_HEIGHT; - ctx.fillRect(rectStart, y, rectWidth, SLICE_HEIGHT); - - if (selected.contains(new Slice.Key(tStart, tEnd - tStart))) { - visibleSelected.add(Highlight.slice(color.border, rectStart, y, rectWidth)); - } - - // Don't render text when we have less than 7px to play with. - if (rectWidth < 7) { - continue; - } - - ctx.setForegroundColor(colors().textMain); - ctx.drawText( - Fonts.Style.Normal, title, rectStart + 2, y + 2, rectWidth - 4, SLICE_HEIGHT - 4); - } else { - double rectWidth = 20; - double y = depth * SLICE_HEIGHT; - double[] diamondX = { rectStart - (rectWidth / 2), rectStart, rectStart + (rectWidth / 2), rectStart}; - double[] diamondY = { y + (SLICE_HEIGHT / 2), y, y + (SLICE_HEIGHT / 2), SLICE_HEIGHT }; - ctx.fillPolygon(diamondX, diamondY, 4); - - if (selected.contains(new Slice.Key(tStart, tEnd - tStart))) { - visibleSelected.add(Highlight.diamond(color.border, diamondX, diamondY)); - } - - ctx.setForegroundColor(colors().textMain); - ctx.drawText( - Fonts.Style.Normal, title.substring(0,1), rectStart - rectWidth/2 + 1, y + 1, rectWidth - 1, SLICE_HEIGHT - 4); - } - } - - for (Highlight highlight : visibleSelected) { - highlight.draw(ctx); - } - - if (hoveredTitle != null) { - ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect( - mouseXpos + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); - - ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoveredTitle, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, mouseYpos + HOVER_PADDING / 2); - if (!hoveredCategory.isEmpty()) { - ctx.setForegroundColor(colors().textAlt); - ctx.drawText(Fonts.Style.Normal, hoveredCategory, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, - mouseYpos + hoveredSize.h / 2, hoveredSize.h / 2); - } - } - } - - private static String buildSliceTitle(String title, ArgSet args) { - Object w = args.get("width"), h = args.get("height"); - return (w == null || h == null) ? title : title + " (" + w + "x" + h + ")"; - } - - @Override - protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - FrameEventsTrack.Data data = track.getData(state.toRequest(), onUiThread()); - if (data == null) { - return Hover.NONE; - } - - switch (data.kind) { - case slices: return sliceHover(data, m, x, y, mods); - case summary: return summaryHover(data, m, x); - default: return Hover.NONE; - } - } - - private Hover summaryHover(FrameEventsTrack.Data data, Fonts.TextMeasurer m, double x) { - long time = state.pxToTime(x); - int bucket = (int)((time - data.request.range.start) / data.bucketSize); - if (bucket < 0 || bucket >= data.numEvents.length) { - return Hover.NONE; - } - - long p = data.numEvents[bucket]; - String text; - if (p == 0) { - text = "Nothing presented"; - } else if (p == 1) { - text = Long.toString(p) + " frame presented"; - } else { - text = Long.toString(p) + " frames presented"; - } - hovered = new HoverCard(bucket, p, text, m.measure(Fonts.Style.Normal, text)); - - double mouseX = state.timeToPx( - data.request.range.start + hovered.bucket * data.bucketSize + data.bucketSize / 2); - double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; - double dy = height; - return new Hover() { - @Override - public Area getRedraw() { - return new Area(mouseX - CURSOR_SIZE, -TRACK_MARGIN, CURSOR_SIZE + HOVER_MARGIN + dx, dy); - } - - @Override - public void stop() { - hovered = null; - } - }; - } - - private Hover sliceHover(FrameEventsTrack.Data data, Fonts.TextMeasurer m, double x, double y, int mods) { - int depth = (int)(y / SLICE_HEIGHT); - if (depth < 0 || depth > buffer.maxDepth) { - return Hover.NONE; - } - - mouseXpos = x; - mouseYpos = depth * SLICE_HEIGHT; - for (int i = 0; i < data.starts.length; i++) { - double ts = state.timeToPx(data.starts[i]); - double endts = state.timeToPx(data.ends[i]); - if (ts == endts) { - endts = ts + 12; - ts -= 12; - } - if (data.depths[i] == depth && x >= ts && x<= endts) { - hoveredTitle = data.titles[i]; - hoveredCategory = data.categories[i]; - if (hoveredTitle.isEmpty()) { - if (hoveredCategory.isEmpty()) { - return Hover.NONE; - } - hoveredTitle = hoveredCategory; - hoveredCategory = ""; - } - hoveredTitle = buildSliceTitle(hoveredTitle, data.args[i]); - - hoveredSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, - m.measure(Fonts.Style.Normal, hoveredTitle), - hoveredCategory.isEmpty() ? Size.ZERO : m.measure(Fonts.Style.Normal, hoveredCategory)); - mouseYpos = Math.max(0, Math.min(mouseYpos - (hoveredSize.h - SLICE_HEIGHT) / 2, - (1 + buffer.maxDepth) * SLICE_HEIGHT - hoveredSize.h)); - long id = data.ids[i]; - return new Hover() { - @Override - public Area getRedraw() { - return new Area( - x + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); - } - - @Override - public void stop() { - hoveredTitle = hoveredCategory = null; - } - - @Override - public Cursor getCursor(Display display) { - return (id < 0) ? null : display.getSystemCursor(SWT.CURSOR_HAND); - } - - @Override - public boolean click() { - if (id < 0) { - return false; - } - if ((mods & SWT.MOD1) == SWT.MOD1) { - state.addSelection(Selection.Kind.FrameEvents, track.getSlice(id)); - } else { - state.setSelection(Selection.Kind.FrameEvents, track.getSlice(id)); - } - return true; - } - }; - } - } - return Hover.NONE; - } - - @Override - public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { - int startDepth = (int)(area.y / SLICE_HEIGHT); - int endDepth = (int)((area.y + area.h) / SLICE_HEIGHT); - if (startDepth == endDepth && area.h / SLICE_HEIGHT < SELECTION_THRESHOLD) { - return; - } - if (((startDepth + 1) * SLICE_HEIGHT - area.y) / SLICE_HEIGHT < SELECTION_THRESHOLD) { - startDepth++; - } - if ((area.y + area.h - endDepth * SLICE_HEIGHT) / SLICE_HEIGHT < SELECTION_THRESHOLD) { - endDepth--; - } - if (startDepth > endDepth) { - return; - } - - if (endDepth >= 0) { - if (endDepth >= buffer.maxDepth) { - endDepth = Integer.MAX_VALUE; - } - - builder.add(Selection.Kind.FrameEvents, - transform(track.getSlices(ts, startDepth, endDepth), FrameEventsTrack.SlicesBuilder::new)); - } - } - - private static class HoverCard { - public final int bucket; - public final long numEvents; - public final String text; - public final Size size; - - public HoverCard(int bucket, long numEvents, String text, Size size) { - this.bucket = bucket; - this.numEvents = numEvents; - this.text = text; - this.size = size; - } - } - - @Override - public FrameEventsSummaryPanel copy() { - return new FrameEventsSummaryPanel(state, buffer, track); - } - - private interface Highlight { - public void draw(RenderContext ctx); - - public static Highlight slice(RGBA color, double x, double y, double w) { - return ctx -> { - ctx.setForegroundColor(color); - ctx.drawRect(x, y, w, SLICE_HEIGHT, BOUNDING_BOX_LINE_WIDTH); - }; - } - - public static Highlight diamond(RGBA color, double[] xs, double[] ys) { - return ctx -> { - ctx.setForegroundColor(color); - ctx.drawPolygon(xs, ys, 4); - }; - } - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/FuchsiaTraceConfigDialog.java b/gapic/src/main/com/google/gapid/perfetto/views/FuchsiaTraceConfigDialog.java new file mode 100644 index 0000000000..f719db65f9 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/FuchsiaTraceConfigDialog.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.widgets.Widgets.createCheckboxTableViewer; +import static com.google.gapid.widgets.Widgets.createComposite; +import static com.google.gapid.widgets.Widgets.createGroup; +import static com.google.gapid.widgets.Widgets.createLabel; +import static com.google.gapid.widgets.Widgets.createLink; +import static com.google.gapid.widgets.Widgets.createTextbox; +import static com.google.gapid.widgets.Widgets.withLayoutData; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.gapid.models.Models; +import com.google.gapid.models.Settings; +import com.google.gapid.proto.SettingsProto; +import com.google.gapid.proto.service.Service; +import com.google.gapid.util.Messages; +import com.google.gapid.widgets.DialogBase; +import com.google.gapid.widgets.Theme; +import com.google.gapid.widgets.Widgets; + +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +/** + * Dialog shown to configure a Fuchsia System trace. + */ +public class FuchsiaTraceConfigDialog extends DialogBase { + private static final String[] FUCHSIA_CATEGORIES = { + "app", "audio", "benchmark", "blobfs","dart", "dart:compiler", "dart:dart", "dart:debugger", + "dart:embedder", "dart:gc", "dart:isolate", "dart:profiler", "dart:vm", "flutter", "gfx", + "input", "kernel:meta", "kernel:sched", "ledger", "magma", "minfs", "modular", "view", + }; + + private final Settings settings; + private final Set currentCategories; + private final List additionalCategories; + + private CheckboxTableViewer categories; + + public FuchsiaTraceConfigDialog(Shell shell, Settings settings, Theme theme) { + super(shell, theme); + this.settings = settings; + this.currentCategories = Sets.newHashSet(settings.fuchsiaTracing().getCategoriesList()); + this.additionalCategories = + Lists.newArrayList(Sets.difference(currentCategories, Sets.newHashSet(FUCHSIA_CATEGORIES))); + Collections.sort(additionalCategories); + } + + public static void showFuchsiaConfigDialog(Shell shell, Models models, Widgets widgets) { + new FuchsiaTraceConfigDialog(shell, models.settings, widgets.theme).open(); + } + + public static String getConfigSummary(Settings settings) { + SettingsProto.FuchsiaTracingOrBuilder f = settings.fuchsiaTracing(); + switch (f.getCategoriesCount()) { + case 0: + return "Default categories"; + case 1: + return "1 category"; + default: + return f.getCategoriesCount() + " categories"; + } + } + + public static Service.FuchsiaTraceConfig.Builder getConfig(Settings settings) { + return Service.FuchsiaTraceConfig.newBuilder() + .addAllCategories(settings.fuchsiaTracing().getCategoriesList()); + } + + @Override + public String getTitle() { + return Messages.CAPTURE_TRACE_FUCHSIA; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite)super.createDialogArea(parent); + Composite container = withLayoutData(createComposite(area, new FillLayout()), + new GridData(SWT.FILL, SWT.FILL, true, true)); + + Group catGroup = createGroup(container, "Categories", new GridLayout(1, false)); + categories = createCheckboxTableViewer(catGroup, SWT.NONE); + categories.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + categories.getTable().setHeaderVisible(false); + categories.setContentProvider(new ArrayContentProvider()); + + updateCategories(); + categories.addCheckStateListener(event -> { + if (event.getChecked()) { + currentCategories.add((String)event.getElement()); + } else { + currentCategories.remove(event.getElement()); + } + }); + + createLink(catGroup, "Select none / default | all", e -> { + switch (e.text) { + case "none / default": + currentCategories.clear(); + categories.setAllChecked(false); + break; + case "all": + currentCategories.clear(); + currentCategories.addAll(Arrays.asList(FUCHSIA_CATEGORIES)); + currentCategories.addAll(additionalCategories); + categories.setAllChecked(true); + break; + } + }); + + Composite custom = withLayoutData(createComposite(catGroup, new GridLayout(3, false)), + new GridData(SWT.FILL, SWT.BOTTOM, true, false)); + withLayoutData(createLabel(custom, "Custom:"), + new GridData(SWT.LEFT, SWT.CENTER, false, false)); + Text customCat = withLayoutData(createTextbox(custom, ""), + new GridData(SWT.FILL, SWT.CENTER, true, false)); + Button customAdd = withLayoutData(Widgets.createButton(custom, "Add", e -> { + String cat = customCat.getText().trim(); + additionalCategories.add(cat); + currentCategories.add(cat); + + categories.add(cat); + categories.setChecked(cat, true); + categories.reveal(cat); + + customCat.setText(""); + ((Button)e.widget).setEnabled(false); + }), new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + customAdd.setEnabled(false); + customCat.addListener(SWT.Modify, e -> { + String cat = customCat.getText().trim(); + customAdd.setEnabled(!cat.isEmpty() && !cat.contains(",")); + }); + + return area; + } + + @Override + protected Point getInitialSize() { + return new Point(convertHorizontalDLUsToPixels(300), convertVerticalDLUsToPixels(250)); + } + + @Override + protected void okPressed() { + SettingsProto.FuchsiaTracing.Builder fuchsia = settings.writeFuchsiaTracing(); + fuchsia.clearCategories(); + fuchsia.addAllCategories(currentCategories); + + super.okPressed(); + } + + private void updateCategories() { + List items = Lists.newArrayList(FUCHSIA_CATEGORIES); + items.addAll(additionalCategories); + categories.setInput(items); + + categories.setCheckedElements(currentCategories.toArray(String[]::new)); + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/GpuQueuePanel.java b/gapic/src/main/com/google/gapid/perfetto/views/GpuQueuePanel.java index dabccc2f39..d8b5754a5b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/GpuQueuePanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/GpuQueuePanel.java @@ -19,7 +19,6 @@ import static com.google.gapid.perfetto.views.StyleConstants.SELECTION_THRESHOLD; import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; import static com.google.gapid.perfetto.views.StyleConstants.colors; -import static com.google.gapid.util.MoreFutures.transform; import com.google.common.collect.Lists; import com.google.common.collect.Sets; @@ -31,9 +30,7 @@ import com.google.gapid.perfetto.models.ArgSet; import com.google.gapid.perfetto.models.GpuInfo; import com.google.gapid.perfetto.models.Selection; -import com.google.gapid.perfetto.models.Selection.CombiningBuilder; import com.google.gapid.perfetto.models.SliceTrack; -import com.google.gapid.perfetto.models.SliceTrack.Slice; import com.google.gapid.perfetto.models.VulkanEventTrack; import org.eclipse.swt.SWT; @@ -93,16 +90,18 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } TimeSpan visible = state.getVisibleTime(); - Selection selected = state.getSelection(Selection.Kind.Gpu); + Selection selected = state.getSelection(Selection.Kind.Gpu); List visibleSelected = Lists.newArrayList(); Set selectedSIds = getSelectedSubmissionIdsInVulkanEventTrack(state); long[] sIds = data.getExtraLongs("submissionIds"); + String[] concatedIds = data.getExtraStrings("concatedIds"); for (int i = 0; i < data.starts.length; i++) { long tStart = data.starts[i]; long tEnd = data.ends[i]; int depth = data.depths[i]; + long id = data.ids[i]; String title = buildSliceTitle(data.titles[i], data.args[i]); if (tEnd <= visible.start || tStart >= visible.end) { @@ -123,10 +122,17 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double ctx.fillRect(rectStart, y, rectWidth, SLICE_HEIGHT); // Highlight GPU queue slice if it's selected or linked by a vulkan api event. - if (selected.contains(new Slice.Key(tStart, tEnd - tStart, depth)) || - (i < sIds.length && selectedSIds.contains(sIds[i]))) { + if (selected.contains(id) || (i < sIds.length && selectedSIds.contains(sIds[i]))) { // Unquantized track. visibleSelected.add(new Highlight(color.border, rectStart, y, rectWidth)); } + if (i < concatedIds.length) { // Quantized track. + for (String cId : concatedIds[i].split(",")) { + if (selected.contains(Long.parseLong(cId))) { + visibleSelected.add(new Highlight(color.border, rectStart, y, rectWidth)); + break; + } + } + } // Don't render text when we have less than 7px to play with. if (rectWidth < 7) { @@ -145,17 +151,20 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } if (hoveredTitle != null) { + double cardW = hoveredSize.w + 2 * HOVER_PADDING; + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - HOVER_MARGIN - cardW; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect( - mouseXpos + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + ctx.fillRect(cardX, mouseYpos, cardW, hoveredSize.h); ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoveredTitle, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, mouseYpos + HOVER_PADDING / 2); + ctx.drawText(Fonts.Style.Normal, hoveredTitle, cardX + HOVER_PADDING, + mouseYpos + HOVER_PADDING / 2); if (!hoveredCategory.isEmpty()) { ctx.setForegroundColor(colors().textAlt); - ctx.drawText(Fonts.Style.Normal, hoveredCategory, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, + ctx.drawText(Fonts.Style.Normal, hoveredCategory, cardX + HOVER_PADDING, mouseYpos + hoveredSize.h / 2, hoveredSize.h / 2); } } @@ -168,8 +177,9 @@ private static String buildSliceTitle(String title, ArgSet args) { } @Override - protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - SliceTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + SliceTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null) { return Hover.NONE; } @@ -183,7 +193,9 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m mouseYpos = depth * SLICE_HEIGHT; long t = state.pxToTime(x); for (int i = 0; i < data.starts.length; i++) { - if (data.depths[i] == depth && data.starts[i] <= t && t <= data.ends[i]) { + long tStart = data.starts[i]; + long tEnd = data.ends[i]; + if (data.depths[i] == depth && tStart <= t && t <= tEnd) { hoveredTitle = data.titles[i]; hoveredCategory = data.categories[i]; if (hoveredTitle.isEmpty()) { @@ -194,6 +206,7 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m hoveredCategory = ""; } hoveredTitle = buildSliceTitle(hoveredTitle, data.args[i]); + hoveredTitle += " (" + TimeSpan.timeToString(tEnd - tStart) + ")"; hoveredSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, m.measure(Fonts.Style.Normal, hoveredTitle), @@ -201,12 +214,18 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m mouseYpos = Math.max(0, Math.min(mouseYpos - (hoveredSize.h - SLICE_HEIGHT) / 2, (1 + queue.maxDepth) * SLICE_HEIGHT - hoveredSize.h)); long id = data.ids[i]; + String concatedId = i < data.getExtraStrings("concatedIds").length ? + data.getExtraStrings("concatedIds")[i] : ""; return new Hover() { @Override public Area getRedraw() { - return new Area( - x + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + double redrawW = HOVER_MARGIN + hoveredSize.w + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, mouseYpos, redrawW, hoveredSize.h); } @Override @@ -216,20 +235,27 @@ public void stop() { @Override public Cursor getCursor(Display display) { - return (id < 0) ? null : display.getSystemCursor(SWT.CURSOR_HAND); + return (id < 0 && concatedId.isEmpty()) ? null : display.getSystemCursor(SWT.CURSOR_HAND); } @Override public boolean click() { - if (id < 0) { - return false; + if (id > 0) { // Track data with no quantization. + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Gpu, track.getSlice(id)); + } else { + state.setSelection(Selection.Kind.Gpu, track.getSlice(id)); + } + return true; + } else if (!concatedId.isEmpty()) { // Track data with quantization. + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Gpu, track.getSlices(concatedId)); + } else { + state.setSelection(Selection.Kind.Gpu, track.getSlices(concatedId)); + } + return true; } - if ((mods & SWT.MOD1) == SWT.MOD1) { - state.addSelection(Selection.Kind.Gpu, track.getSlice(id)); - } else { - state.setSelection(Selection.Kind.Gpu, track.getSlice(id)); - } - return true; + return false; } }; } @@ -238,7 +264,7 @@ public boolean click() { } @Override - public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { int startDepth = (int)(area.y / SLICE_HEIGHT); int endDepth = (int)((area.y + area.h) / SLICE_HEIGHT); if (startDepth == endDepth && area.h / SLICE_HEIGHT < SELECTION_THRESHOLD) { @@ -258,19 +284,14 @@ public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { if (endDepth >= queue.maxDepth) { endDepth = Integer.MAX_VALUE; } - - builder.add(Selection.Kind.Gpu, transform( - track.getSlices(ts, startDepth, endDepth), - SliceTrack.SlicesBuilder::new)); + builder.add(Selection.Kind.Gpu, track.getSlices(ts, startDepth, endDepth)); } } private static Set getSelectedSubmissionIdsInVulkanEventTrack(State state) { - Selection selection = state.getSelection(Selection.Kind.VulkanEvent); + Selection selection = state.getSelection(Selection.Kind.VulkanEvent); Set res = Sets.newHashSet(); // On Vulkan Event Track. - if (selection instanceof VulkanEventTrack.Slice) { - res = Sets.newHashSet(((VulkanEventTrack.Slice)selection).submissionId); - } else if (selection instanceof VulkanEventTrack.Slices) { + if (selection instanceof VulkanEventTrack.Slices) { res = ((VulkanEventTrack.Slices)selection).getSubmissionIds(); } return res; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/KeyboardMouseHelpDialog.java b/gapic/src/main/com/google/gapid/perfetto/views/KeyboardMouseHelpDialog.java index 255cc581e2..3f2d3617b2 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/KeyboardMouseHelpDialog.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/KeyboardMouseHelpDialog.java @@ -26,18 +26,14 @@ import com.google.gapid.util.Messages; import com.google.gapid.widgets.DialogBase; import com.google.gapid.widgets.Theme; +import com.google.gapid.widgets.Widgets; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.swt.SWT; -import org.eclipse.swt.SWTError; -import org.eclipse.swt.browser.Browser; -import org.eclipse.swt.browser.LocationAdapter; -import org.eclipse.swt.browser.LocationEvent; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; import java.io.IOException; import java.util.logging.Logger; @@ -60,29 +56,9 @@ public String getTitle() { @Override protected Control createDialogArea(Composite parent) { Composite area = (Composite)super.createDialogArea(parent); - - Browser browser; - try { - browser = new Browser(area, SWT.NONE); - } catch (SWTError e) { - // Failed to initialize the browser. Show it as a plain text widget. - Text text = new Text( - area, SWT.MULTI | SWT.READ_ONLY | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.setText(readKeyboardMouseHelp()); - return text; - } - - browser.setLayoutData(withSizeHints(new GridData(SWT.FILL, SWT.FILL, true, true), 1024, 768)); - browser.setText(readKeyboardMouseHelp()); - browser.addLocationListener(new LocationAdapter() { - @Override - public void changing(LocationEvent event) { - if ("about:blank".equals(event.location)) { - browser.setText(readKeyboardMouseHelp()); - } - } - }); + Control browser = Widgets.createBrowser(area, readKeyboardMouseHelp()); + browser.setLayoutData( + withSizeHints(new GridData(SWT.FILL, SWT.FILL, true, true), 1024, 768)); return area; } @@ -102,4 +78,4 @@ protected static String readKeyboardMouseHelp() { return "Failed to load help."; } } -} \ No newline at end of file +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/MemorySelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/MemorySelectionView.java new file mode 100644 index 0000000000..5763e7b3cd --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/MemorySelectionView.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createTableColumn; +import static com.google.gapid.widgets.Widgets.createTableViewer; +import static com.google.gapid.widgets.Widgets.packColumns; + +import com.google.gapid.perfetto.models.MemorySummaryTrack; +import com.google.gapid.perfetto.models.ProcessMemoryTrack; + +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +import java.util.function.Consumer; + +public class MemorySelectionView extends Composite { + private MemorySelectionView(Composite parent, int numRows, Consumer createColumns) { + super(parent, SWT.NONE); + setLayout(new FillLayout()); + + TableViewer viewer = createTableViewer(this, SWT.NONE); + viewer.setContentProvider(new ArrayContentProvider()); + viewer.setLabelProvider(new LabelProvider()); + + createColumns.accept(viewer); + + Integer[] rows = new Integer[numRows]; + for (int i = 0; i < rows.length; i++) { + rows[i] = i; + } + viewer.setInput(rows); + packColumns(viewer.getTable()); + } + + public MemorySelectionView(Composite parent, State state, MemorySummaryTrack.Values sel) { + this(parent, sel.ts.length, viewer -> { + createTableColumn( + viewer, "Time", r -> timeToString(sel.ts[(Integer)r] - state.getTraceTime().start)); + createTableColumn(viewer, "Dur", r -> timeToString(sel.dur[(Integer)r])); + createTableColumn(viewer, "Total", r -> String.valueOf(sel.total[(Integer)r])); + createTableColumn(viewer, "Unused", r -> String.valueOf(sel.unused[(Integer)r])); + createTableColumn(viewer, "BuffCache", r -> String.valueOf(sel.buffCache[(Integer)r])); + }); + } + + public MemorySelectionView(Composite parent, State state, ProcessMemoryTrack.Values sel) { + this(parent, sel.ts.length, viewer -> { + createTableColumn( + viewer, "Time", r -> timeToString(sel.ts[(Integer)r] - state.getTraceTime().start)); + createTableColumn(viewer, "Dur", r -> timeToString(sel.dur[(Integer)r])); + createTableColumn(viewer, "File", r -> String.valueOf(sel.file[(Integer)r])); + createTableColumn(viewer, "Anon", r -> String.valueOf(sel.anon[(Integer)r])); + createTableColumn(viewer, "Shared", r -> String.valueOf(sel.shared[(Integer)r])); + createTableColumn(viewer, "Swap", r -> String.valueOf(sel.swap[(Integer)r])); + }); + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/MemorySummaryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/MemorySummaryPanel.java index 8047d17559..c2704b1ee3 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/MemorySummaryPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/MemorySummaryPanel.java @@ -15,29 +15,40 @@ */ package com.google.gapid.perfetto.views; +import static com.google.gapid.perfetto.Unit.bytesToString; import static com.google.gapid.perfetto.views.Loading.drawLoading; import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; import static com.google.gapid.perfetto.views.StyleConstants.colors; import static com.google.gapid.perfetto.views.StyleConstants.memoryBuffersGradient; import static com.google.gapid.perfetto.views.StyleConstants.memoryUsedGradient; +import com.google.common.collect.Lists; +import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.MemorySummaryTrack; +import com.google.gapid.perfetto.models.Selection; +import com.google.gapid.perfetto.models.Selection.Kind; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.widgets.Display; + +import java.util.List; /** * Displays information about the system memory usage. */ -public class MemorySummaryPanel extends TrackPanel { +public class MemorySummaryPanel extends TrackPanel implements Selectable { private static final double HEIGHT = 80; private static final double HOVER_MARGIN = 10; private static final double HOVER_PADDING = 4; private static final double CURSOR_SIZE = 5; private static final double LEGEND_SIZE = 8; - private final MemorySummaryTrack track; + protected final MemorySummaryTrack track; protected HoverCard hovered = null; protected double mouseXpos, mouseYpos; @@ -72,6 +83,9 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou return; } + Selection selected = state.getSelection(Selection.Kind.Memory); + List visibleSelected = Lists.newArrayList(); + memoryBuffersGradient().applyBase(ctx); ctx.path(path -> { path.moveTo(0, h); @@ -83,6 +97,9 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou path.lineTo(nextX, nextY); lastX = nextX; lastY = nextY; + if (selected.contains(data.id[i])) { + visibleSelected.add(i); + } } path.lineTo(lastX, h); path.close(); @@ -113,12 +130,26 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou ctx.setForegroundColor(colors().textMain); ctx.drawText(Fonts.Style.Normal, label, 4, 4); + // Draw highlight line after the whole graph is rendered, so that the highlight is on the top. + for (int index : visibleSelected) { + double startX = state.timeToPx(data.ts[index]); + double endX = (index >= data.ts.length - 1) ? startX : state.timeToPx(data.ts[index + 1]); + ctx.setBackgroundColor(memoryBuffersGradient().highlight); + ctx.fillRect(startX, h * data.unused[index] / data.total[index] - 1, endX - startX, 3); + ctx.setBackgroundColor(memoryUsedGradient().highlight); + ctx.fillRect(startX, h * (data.unused[index] + data.buffCache[index]) / data.total[index] - 1, endX - startX, 3); + } + if (hovered != null) { + double cardW = hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE; + double cardX = mouseXpos + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - CURSOR_SIZE / 2 - HOVER_MARGIN - cardW; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(mouseXpos + HOVER_MARGIN, mouseYpos, - hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE, hovered.allSize.h); + ctx.fillRect(cardX, mouseYpos, cardW, hovered.allSize.h); - double x = mouseXpos + HOVER_MARGIN + HOVER_PADDING, y = mouseYpos; + double x = cardX + HOVER_PADDING, y = mouseYpos; double dy = hovered.allSize.h / 4; ctx.setBackgroundColor(colors().background); ctx.fillRect(x, y + 1 * dy + (dy - LEGEND_SIZE) / 2, LEGEND_SIZE, LEGEND_SIZE); @@ -147,8 +178,9 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou } @Override - protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - MemorySummaryTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + MemorySummaryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null || data.ts.length == 0) { return Hover.NONE; } @@ -164,42 +196,53 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m } } + long id = data.id[idx]; hovered = new HoverCard(m, data.total[idx], data.unused[idx], data.buffCache[idx]); mouseXpos = x; mouseYpos = (height - 2 * TRACK_MARGIN - hovered.allSize.h) / 2; return new Hover() { @Override public Area getRedraw() { - return new Area(mouseXpos - CURSOR_SIZE, -TRACK_MARGIN, - CURSOR_SIZE + HOVER_MARGIN + hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE, - HEIGHT + 2 * TRACK_MARGIN); + double redrawW = CURSOR_SIZE + HOVER_MARGIN + hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE; + double redrawX = mouseXpos - CURSOR_SIZE / 2; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = mouseXpos + CURSOR_SIZE / 2 - redrawW; + // If the hover card is drawn on the left side of the hover point, when moving the mouse + // from left to right, the right edge of the cursor doesn't seem to get redrawn all the + // time, this looks like a precision issue. This also happens when cursor is now on the + // right side of the hover card, and the mouse moving from right to left there seems to + // be a precision issue on the right edge of the cursor, hence extend the redraw with by + // plusing the radius of the cursor. + redrawW += CURSOR_SIZE / 2; + } + return new Area(redrawX, -TRACK_MARGIN, redrawW, HEIGHT + 2 * TRACK_MARGIN); } @Override public void stop() { hovered = null; } + + @Override + public Cursor getCursor(Display display) { + return display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Kind.Memory, track.getValue(id)); + } else { + state.setSelection(Kind.Memory, track.getValue(id)); + } + return true; + } }; } - protected static String bytesToString(long val) { - if (val < 1024 + 512) { - return val + "b"; - } - double v = val / 1024.0; - if (v < 1024 + 512) { - return String.format("%.1fKb", v); - } - v /= 1024; - if (v < 1024 + 512) { - return String.format("%.1fMb", v); - } - v /= 1024; - if (v < 1024 + 512) { - return String.format("%.1fGb", v); - } - v /= 1024; - return String.format("%.1fTb", v); + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Selection.Kind.Memory, track.getValues(ts)); } private static class HoverCard { diff --git a/gapic/src/main/com/google/gapid/perfetto/views/MultiSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/MultiSelectionView.java index b459648bfd..68a4b4bfda 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/MultiSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/MultiSelectionView.java @@ -31,14 +31,19 @@ * Displays multiple different selections. */ public class MultiSelectionView extends Composite { + public TabFolder folder; + public MultiSelectionView( - Composite parent, Map, Selection> selections, State state) { + Composite parent, Map> selections, State state) { super(parent, SWT.NONE); setLayout(new FillLayout()); - TabFolder folder = createStandardTabFolder(this); + folder = createStandardTabFolder(this); for (Selection s : selections.values()) { - createStandardTabItem(folder, s.getTitle(), s.buildUi(folder, state)); + Composite composite = s.buildUi(folder, state); + if (composite != null) { + createStandardTabItem(folder, s.getTitle(), composite); + } } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/PinnedTracks.java b/gapic/src/main/com/google/gapid/perfetto/views/PinnedTracks.java index 472383d08d..567191815c 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/PinnedTracks.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/PinnedTracks.java @@ -16,7 +16,7 @@ package com.google.gapid.perfetto.views; import com.google.gapid.perfetto.canvas.Area; -import com.google.gapid.perfetto.canvas.Fonts.TextMeasurer; +import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.Panel; import com.google.gapid.perfetto.canvas.PanelGroup; import com.google.gapid.perfetto.canvas.RenderContext; @@ -65,8 +65,9 @@ public Dragger onDragStart(double x, double y, int mods) { } @Override - public Hover onMouseMove(TextMeasurer m, double x, double y, int mods) { - return group.onMouseMove(m, x, y, mods); + public Hover onMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + return group.onMouseMove(m, repainter, x, y, mods); } @Override diff --git a/gapic/src/main/com/google/gapid/perfetto/views/PowerSummaryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/PowerSummaryPanel.java new file mode 100644 index 0000000000..ac1fcc0ac6 --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/PowerSummaryPanel.java @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2022 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.views.Loading.drawLoading; +import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; +import static com.google.gapid.perfetto.views.StyleConstants.colors; +import static com.google.gapid.perfetto.views.StyleConstants.mainGradient; + +import com.google.gapid.perfetto.Unit; +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.Fonts; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.models.PowerSummaryTrack; +import com.google.gapid.perfetto.models.Selection; + +public class PowerSummaryPanel extends TrackPanel { + private static final double HOVER_MARGIN = 10; + private static final double HOVER_PADDING = 4; + private static final double CURSOR_SIZE = 5; + private static final double TRACK_HEIGHT = 80; + private static Unit unit; + + protected final PowerSummaryTrack track; + protected HoverCard hovered = null; + + public PowerSummaryPanel(State state, PowerSummaryTrack track) { + super(state); + this.track = track; + this.unit = track.unit; + } + + private double calculateYCoordinate(double value) { + double range = + (track.minValue == track.maxValue && track.minValue == 0) + ? 1 + : (track.maxValue - track.minValue); + return (TRACK_HEIGHT - 1) * (1 - (value - track.minValue) / range); + } + + @Override + public PowerSummaryPanel copy() { + return new PowerSummaryPanel(state, track); + } + + @Override + public String getTitle() { + return "Power Usage"; + } + + @Override + public String getSubTitle() { + return track.getNumPowerRailTracks() + " power rail tracks"; + } + + @Override + public String getTooltip() { + return "Total Power Usage"; + } + + @Override + public double getHeight() { + return TRACK_HEIGHT; + } + + @Override + protected void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { + ctx.trace( + "PowerSummary", + () -> { + PowerSummaryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + drawLoading(ctx, data, state, h); + + if (data == null || data.ts.length == 0) { + return; + } + + Selection selected = state.getSelection(Selection.Kind.Counter); + mainGradient().applyBaseAndBorder(ctx); + ctx.path( + path -> { + double lastX = state.timeToPx(data.ts[0]); + double lastY = h; + path.moveTo(lastX, lastY); + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = calculateYCoordinate(data.values[i]); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + } + + path.lineTo(lastX, h); + ctx.fillPath(path); + ctx.drawPath(path); + }); + + if (hovered != null) { + double y = calculateYCoordinate(hovered.value); + ctx.setBackgroundColor(mainGradient().highlight); + ctx.fillRect(hovered.startX, y - 1, hovered.endX - hovered.startX, TRACK_HEIGHT - y + 1); + ctx.setForegroundColor(colors().textMain); + ctx.drawCircle(hovered.mouseX, y, CURSOR_SIZE / 2); + + ctx.setBackgroundColor(colors().hoverBackground); + double bgH = Math.max(hovered.size.h, TRACK_HEIGHT); + // The left x-axis coordinate of HoverCard. + double hx = hovered.mouseX + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (hx >= w - (2 * HOVER_PADDING + hovered.size.w)) { + hx = + hovered.mouseX + - CURSOR_SIZE / 2 + - HOVER_MARGIN + - 2 * HOVER_PADDING + - hovered.size.w; + } + ctx.fillRect( + hx, Math.min((TRACK_HEIGHT - bgH) / 2, 0), 2 * HOVER_PADDING + hovered.size.w, bgH); + ctx.setForegroundColor(colors().textMain); + // The left x-axis coordinate of the left labels. + double x = hx + HOVER_PADDING; + y = (TRACK_HEIGHT - hovered.size.h) / 2; + // The difference between the x-axis coordinate of the left labels and the right labels. + double dx = hovered.leftWidth + HOVER_PADDING, dy = hovered.size.h / 2; + ctx.drawText(Fonts.Style.Normal, "Value:", x, y); + ctx.drawText(Fonts.Style.Normal, hovered.minLabel, x + dx, y); + ctx.drawText(Fonts.Style.Normal, hovered.maxLabel, x + dx, y + dy); + + x = hx + HOVER_PADDING + hovered.leftWidth; + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.valueLabel, x, y, dy); + + x = hx + HOVER_PADDING + hovered.size.w; + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.min, x, y, dy); + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.max, x, y + dy, dy); + } + }); + } + + @Override + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + PowerSummaryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + if (data == null || data.ts.length == 0) { + return Hover.NONE; + } + + long time = state.pxToTime(x); + if (time < data.ts[0]) { + return Hover.NONE; + } + + int idx = 0; + for (; idx < data.ts.length - 1; idx++) { + if (data.ts[idx + 1] > time) { + break; + } + } + + if (idx >= data.ts.length) { + return Hover.NONE; + } + + double startX = state.timeToPx(data.ts[idx]); + double endX = + (idx >= data.ts.length - 1) + ? startX + : state.timeToPx(data.ts[idx + 1]); // Moving endX to startX when + hovered = new HoverCard(m, track.minValue, track.maxValue, data.values[idx], startX, endX, x); + + return new Hover() { + @Override + public Area getRedraw() { + // The area of the sample can be larger than the hover card. Hence the redraw area needs to + // at least cover the whole sample because when we move between samples, the highlight of + // the previous sample needs to be redrawn without highlight. If the redraw area only + // covers the hover card then the area that is not intersected with the hover card will + // not be redrawn, and hence the highlight remains in that area. + + // First, calculate the default left boundary x-axis coordinate and width of the + // redrawn area. + final double defaultX = hovered.mouseX - CURSOR_SIZE / 2; + final double defaultW = + CURSOR_SIZE + HOVER_MARGIN + HOVER_PADDING + hovered.size.w + HOVER_PADDING; + + // Assuming the hover card is drawn on the right of the hover point. + + // Determine the x-axis coordinate of the left boundary of the redrawn area. + double redrawLx = Math.min(defaultX, startX); + + // Determine the x-axis coordinate of the right boundary of the redrawn area by comparing + // the right end of the sample with the right boundary of the default redrawn area. + double redrawRx = Math.max(defaultX + defaultW, endX); + + // Calculate the real redrawn width. + double redrawW = redrawRx - redrawLx; + + if (defaultX >= state.getWidth() - defaultW) { + + // Re-calculate the left boundary of the redrawn area by comparing the start of the + // sample with the left boundary of the default redrawn area when the hover card is drawn + // on the left side of the hover point. + redrawLx = Math.min(startX, hovered.mouseX + CURSOR_SIZE / 2 - defaultW); + + // Re-calculate the right boundary of the redrawn area by comparing the end of the sample + // with the right boundary of the default redrawn area when the hover card is drawn on + // the left side of the hover point. + redrawRx = Math.max(hovered.mouseX + CURSOR_SIZE / 2, endX); + + // Finally, re-calculate the redrawn width, plus radius of the cursor to avoid the + // precision issue at the right edge of the cursor. + redrawW = redrawRx - redrawLx + CURSOR_SIZE / 2; + } + + return new Area(redrawLx, -TRACK_MARGIN, redrawW, TRACK_HEIGHT + 2 * TRACK_MARGIN); + } + + @Override + public void stop() { + hovered = null; + } + }; + } + + private static class HoverCard { + public final double value; + public final double startX, endX; + public final double mouseX; + public final String valueLabel; + public final String min, max; + public final String minLabel, maxLabel; + public final double leftWidth; + public final Size size; + + public HoverCard( + Fonts.TextMeasurer tm, + Double minValue, + Double maxValue, + double value, + double startX, + double endX, + double mouseX) { + this.value = value; + this.startX = startX; + this.endX = endX; + this.mouseX = mouseX; + this.valueLabel = unit.format(value); + this.min = unit.format(minValue); + this.max = unit.format(maxValue); + + this.minLabel = "Trace Min:"; + this.maxLabel = "Trace Max:"; + + Size valueSize = tm.measure(Fonts.Style.Normal, valueLabel); + Size minSize = tm.measure(Fonts.Style.Normal, min); + Size maxSize = tm.measure(Fonts.Style.Normal, max); + + double leftLabel = tm.measure(Fonts.Style.Normal, "Value:").w; + double rightLabel = + Math.max( + tm.measure(Fonts.Style.Normal, minLabel).w, + tm.measure(Fonts.Style.Normal, maxLabel).w) + + HOVER_PADDING; + this.leftWidth = leftLabel + valueSize.w; + this.size = + new Size( + leftWidth + HOVER_PADDING + rightLabel + Math.max(minSize.w, maxSize.w), + Math.max(valueSize.h, minSize.h + maxSize.h) + HOVER_PADDING); + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/ProcessMemoryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/ProcessMemoryPanel.java new file mode 100644 index 0000000000..1ecb9fef9c --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/ProcessMemoryPanel.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.perfetto.views; + +import static com.google.gapid.perfetto.Unit.bytesToString; +import static com.google.gapid.perfetto.views.Loading.drawLoading; +import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; +import static com.google.gapid.perfetto.views.StyleConstants.colors; +import static com.google.gapid.perfetto.views.StyleConstants.memoryRssAnonGradient; +import static com.google.gapid.perfetto.views.StyleConstants.memoryRssFileGradient; +import static com.google.gapid.perfetto.views.StyleConstants.memoryRssSharedGradient; +import static com.google.gapid.perfetto.views.StyleConstants.memorySwapGradient; + +import com.google.common.collect.Lists; +import com.google.gapid.perfetto.TimeSpan; +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.Fonts; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.models.ProcessMemoryTrack; +import com.google.gapid.perfetto.models.Selection; +import com.google.gapid.perfetto.models.Selection.Kind; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Cursor; +import org.eclipse.swt.widgets.Display; + +import java.util.List; + +public class ProcessMemoryPanel extends TrackPanel implements Selectable { + private static final double HEIGHT = 80; + private static final double HOVER_MARGIN = 10; + private static final double HOVER_PADDING = 4; + private static final double CURSOR_SIZE = 5; + private static final double LEGEND_SIZE = 8; + + protected final ProcessMemoryTrack track; + protected HoverCard hovered = null; + protected double mouseXpos, mouseYpos; + + public ProcessMemoryPanel(State state, ProcessMemoryTrack track) { + super(state); + this.track = track; + } + + @Override + public ProcessMemoryPanel copy() { + return new ProcessMemoryPanel(state, track); + } + + @Override + public String getTitle() { + return "Memory Usage"; + } + + @Override + public double getHeight() { + return HEIGHT; + } + + @Override + protected void renderTrack(RenderContext ctx, Repainter repainter, double w, double h) { + ctx.trace("ProcessMemory", () -> { + ProcessMemoryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + drawLoading(ctx, data, state, h); + + if (data == null || data.ts.length == 0) { + return; + } + + Selection selected = state.getSelection(Selection.Kind.ProcessMemory); + List visibleSelected = Lists.newArrayList(); + long maxUsage = track.getMaxUsage(), maxSwap = Math.max(maxUsage, track.getMaxSwap()); + + memoryRssSharedGradient().applyBase(ctx); + ctx.path(path -> { + path.moveTo(0, h); + double lastX = 0, lastY = h; + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = h - (h * (data.file[i] + data.anon[i] + data.shared[i]) / maxUsage); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + if (selected.contains(data.id[i])) { + visibleSelected.add(i); + } + } + path.lineTo(lastX, h); + path.close(); + ctx.fillPath(path); + }); + + memoryRssAnonGradient().applyBase(ctx); + ctx.path(path -> { + path.moveTo(0, h); + double lastX = 0, lastY = h; + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = h - (h * (data.file[i] + data.anon[i]) / maxUsage); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + } + path.lineTo(lastX, h); + path.close(); + ctx.fillPath(path); + }); + + memoryRssFileGradient().applyBase(ctx); + ctx.path(path -> { + path.moveTo(0, h); + double lastX = 0, lastY = h; + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = h - (h * (data.file[i]) / maxUsage); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + } + path.lineTo(lastX, h); + path.close(); + ctx.fillPath(path); + }); + + memorySwapGradient().applyBorder(ctx); + ctx.path(path -> { + double lastX = state.timeToPx(data.ts[0]), lastY = h - (h * data.swap[0] / maxSwap); + path.moveTo(lastX, lastY); + for (int i = 0; i < data.ts.length; i++) { + double nextX = state.timeToPx(data.ts[i]); + double nextY = h - (h * data.swap[i] / maxSwap); + path.lineTo(nextX, lastY); + path.lineTo(nextX, nextY); + lastX = nextX; + lastY = nextY; + } + path.lineTo(lastX, h); + ctx.drawPath(path); + }); + + // Draw highlight line after the whole graph is rendered, so that the highlight is on the top. + for (int index : visibleSelected) { + double startX = state.timeToPx(data.ts[index]); + double endX = (index >= data.ts.length - 1) ? startX : state.timeToPx(data.ts[index + 1]); + ctx.setBackgroundColor(memoryRssSharedGradient().highlight); + ctx.fillRect(startX, h - h * (data.file[index] + data.anon[index] + data.shared[index]) / maxUsage - 1, endX - startX, 3); + ctx.setBackgroundColor(memoryRssAnonGradient().highlight); + ctx.fillRect(startX, h - h * (data.file[index] + data.anon[index]) / maxUsage - 1, endX - startX, 3); + ctx.setBackgroundColor(memoryRssFileGradient().highlight); + ctx.fillRect(startX, h - h * data.file[index] / maxUsage - 1, endX - startX, 3); + ctx.setBackgroundColor(memorySwapGradient().highlight); + ctx.fillRect(startX, h - h * data.swap[index] / maxSwap - 1, endX - startX, 3); + } + + + if (hovered != null) { + double cardW = hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE; + double cardX = mouseXpos + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - CURSOR_SIZE / 2 - HOVER_MARGIN - cardW; + } + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(cardX, mouseYpos, cardW, hovered.allSize.h); + + double x = cardX + HOVER_PADDING, y = mouseYpos; + double dy = hovered.allSize.h / 4; + memoryRssSharedGradient().applyBase(ctx); + ctx.fillRect(x, y + 0 * dy + (dy - LEGEND_SIZE) / 2, LEGEND_SIZE, LEGEND_SIZE); + memoryRssAnonGradient().applyBase(ctx); + ctx.fillRect(x, y + 1 * dy + (dy - LEGEND_SIZE) / 2, LEGEND_SIZE, LEGEND_SIZE); + memoryRssFileGradient().applyBase(ctx); + ctx.fillRect(x, y + 2 * dy + (dy - LEGEND_SIZE) / 2, LEGEND_SIZE, LEGEND_SIZE); + memorySwapGradient().applyBase(ctx); + ctx.fillRect(x, y + 3 * dy + (dy - LEGEND_SIZE) / 2, LEGEND_SIZE, LEGEND_SIZE); + + x += LEGEND_SIZE + HOVER_PADDING; + ctx.setForegroundColor(colors().textMain); + ctx.drawText(Fonts.Style.Bold, HoverCard.SHARED_LABEL, x, y + 0 * dy, dy); + ctx.drawText(Fonts.Style.Bold, HoverCard.ANON_LABEL, x, y + 1 * dy, dy); + ctx.drawText(Fonts.Style.Bold, HoverCard.FILE_LABEL, x, y + 2 * dy, dy); + ctx.drawText(Fonts.Style.Bold, HoverCard.SWAP_LABEL, x, y + 3 * dy, dy); + + x += hovered.labelSize.w + HOVER_PADDING + hovered.valueSize.w; + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.sharedS, x, y + 0 * dy, dy); + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.anonS, x, y + 1 * dy, dy); + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.fileS, x, y + 2 * dy, dy); + ctx.drawTextRightJustified(Fonts.Style.Normal, hovered.swapS, x, y + 3 * dy, dy); + + ctx.drawCircle(mouseXpos, h - h * (hovered.file + hovered.anon + hovered.shared) / maxUsage, + CURSOR_SIZE / 2); + ctx.drawCircle( + mouseXpos, h - h * (hovered.file + hovered.anon) / maxUsage, CURSOR_SIZE / 2); + ctx.drawCircle(mouseXpos, h - h * hovered.file / maxUsage, CURSOR_SIZE / 2); + ctx.drawCircle(mouseXpos, h - h * hovered.swap / maxSwap, CURSOR_SIZE / 2); + } + }); + } + + @Override + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + ProcessMemoryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); + if (data == null || data.ts.length == 0) { + return Hover.NONE; + } + + long time = state.pxToTime(x); + if (time < data.ts[0] || time > data.ts[data.ts.length - 1]) { + return Hover.NONE; + } + int idx = 0; + for (; idx < data.ts.length - 1; idx++) { + if (data.ts[idx + 1] > time) { + break; + } + } + + long id = data.id[idx]; + hovered = new HoverCard(m, data.shared[idx], data.anon[idx], data.file[idx], data.swap[idx]); + mouseXpos = x; + mouseYpos = (height - 2 * TRACK_MARGIN - hovered.allSize.h) / 2; + return new Hover() { + @Override + public Area getRedraw() { + double redrawW = CURSOR_SIZE + HOVER_MARGIN + hovered.allSize.w + 3 * HOVER_PADDING + LEGEND_SIZE; + double redrawX = mouseXpos - CURSOR_SIZE / 2; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = mouseXpos + CURSOR_SIZE / 2 - redrawW; + // If the hover card is drawn on the left side of the hover point, when moving the mouse + // from left to right, the right edge of the cursor doesn't seem to get redrawn all the + // time, this looks like a precision issue. This also happens when cursor is now on the + // right side of the hover card, and the mouse moving from right to left there seems to + // be a precision issue on the right edge of the cursor, hence extend the redraw with by + // plusing the radius of the cursor. + redrawW += CURSOR_SIZE / 2; + } + return new Area(redrawX, -TRACK_MARGIN, redrawW, HEIGHT + 2 * TRACK_MARGIN); + } + + @Override + public void stop() { + hovered = null; + } + + @Override + public Cursor getCursor(Display display) { + return display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Kind.ProcessMemory, track.getValue(id)); + } else { + state.setSelection(Kind.ProcessMemory, track.getValue(id)); + } + return true; + } + }; + } + + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Selection.Kind.ProcessMemory, track.getValues(ts)); + } + + private static class HoverCard { + public static final String SHARED_LABEL = "Shared: "; + public static final String ANON_LABEL = "Anon: "; + public static final String FILE_LABEL = "File: "; + public static final String SWAP_LABEL = "Swap: "; + + public final long shared; + public final long anon; + public final long file; + public final long swap; + + public final String sharedS; + public final String anonS; + public final String fileS; + public final String swapS; + + public final Size valueSize; + public final Size labelSize; + public final Size allSize; + + public HoverCard(Fonts.TextMeasurer tm, long shared, long anon, long file, long swap) { + this.shared = shared; + this.anon = anon; + this.file = file; + this.swap = swap; + this.sharedS = bytesToString(shared); + this.anonS = bytesToString(anon); + this.fileS = bytesToString(file); + this.swapS = bytesToString(swap); + + this.labelSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, + tm.measure(Fonts.Style.Bold, SHARED_LABEL), + tm.measure(Fonts.Style.Bold, ANON_LABEL), + tm.measure(Fonts.Style.Bold, FILE_LABEL), + tm.measure(Fonts.Style.Bold, SWAP_LABEL)); + + this.valueSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, + tm.measure(Fonts.Style.Normal, this.sharedS), + tm.measure(Fonts.Style.Normal, this.anonS), + tm.measure(Fonts.Style.Normal, this.fileS), + tm.measure(Fonts.Style.Normal, this.swapS)); + + this.allSize = + new Size(labelSize.w + HOVER_PADDING + valueSize.w, Math.max(labelSize.h, valueSize.h)); + } + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/ProcessSummaryPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/ProcessSummaryPanel.java index 095cc86d08..4331811f6f 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/ProcessSummaryPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/ProcessSummaryPanel.java @@ -20,17 +20,21 @@ import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; import static com.google.gapid.perfetto.views.StyleConstants.colors; import static com.google.gapid.perfetto.views.StyleConstants.mainGradient; +import static com.google.gapid.util.MoreFutures.transform; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.CpuInfo; +import com.google.gapid.perfetto.models.CpuTrack.Slices; import com.google.gapid.perfetto.models.ProcessSummaryTrack; import com.google.gapid.perfetto.models.Selection; import com.google.gapid.perfetto.models.ThreadInfo; +import com.google.gapid.util.Arrays; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Cursor; @@ -42,7 +46,7 @@ /** * Displays the CPU usage summary of a process, aggregating all threads. */ -public class ProcessSummaryPanel extends TrackPanel { +public class ProcessSummaryPanel extends TrackPanel implements Selectable { private static final double HEIGHT = 50; private static final double HOVER_MARGIN = 10; private static final double HOVER_PADDING = 4; @@ -98,7 +102,7 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } switch (data.kind) { - case slice: renderSlices(ctx, data, h); break; + case slice: renderSlices(ctx, data, w, h); break; case summary: renderSummary(ctx, data, w, h); break; } }); @@ -109,6 +113,8 @@ private void renderSummary( // TODO: dedupe with CpuRenderer long tStart = data.request.range.start; int start = Math.max(0, (int)((state.getVisibleTime().start - tStart) / data.bucketSize)); + Selection selected = state.getSelection(Selection.Kind.Cpu); + List visibleSelected = Lists.newArrayList(); mainGradient().applyBaseAndBorder(ctx); ctx.path(path -> { @@ -120,6 +126,12 @@ private void renderSummary( path.lineTo(x, y); path.lineTo(x, nextY); y = nextY; + for (String id : Arrays.getOrDefault(data.concatedIds, i, "").split(",")) { + if (!id.isEmpty() && !selected.isEmpty() && selected.contains(Long.parseLong(id))) { + visibleSelected.add(i); + break; + } + } } path.lineTo(x, h); path.close(); @@ -127,26 +139,36 @@ private void renderSummary( ctx.drawPath(path); }); + // Draw Highlight line after the whole graph is rendered, so that the highlight is on the top. + ctx.setBackgroundColor(mainGradient().highlight); + for (int index : visibleSelected) { + ctx.fillRect(state.timeToPx(tStart + index * data.bucketSize), + Math.round(h * (1 - data.utilizations[index])) - 1, + state.durationToDeltaPx(data.bucketSize), 3); + } + if (hovered != null && hovered.bucket >= start) { double x = state.timeToPx(tStart + hovered.bucket * data.bucketSize + data.bucketSize / 2); - if (x < w) { - double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; - double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; - ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(x + HOVER_MARGIN, h - HOVER_PADDING - dy, dx, dy); - ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hovered.text, x + HOVER_MARGIN + HOVER_PADDING, h - dy); - - ctx.setForegroundColor(colors().textMain); - ctx.drawCircle(x, h * (1 - hovered.utilization), CURSOR_SIZE / 2); + double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; + double dy = HOVER_PADDING + hovered.size.h + HOVER_PADDING; + double cardX = x + CURSOR_SIZE / 2 + HOVER_MARGIN; + if (cardX >= w - dx) { + cardX = x - CURSOR_SIZE / 2 - HOVER_MARGIN - dx; } + ctx.setBackgroundColor(colors().hoverBackground); + ctx.fillRect(cardX, h - HOVER_PADDING - dy, dx, dy); + ctx.setForegroundColor(colors().textMain); + ctx.drawText(Fonts.Style.Normal, hovered.text, cardX + HOVER_PADDING, h - dy); + + ctx.setForegroundColor(colors().textMain); + ctx.drawCircle(x, h * (1 - hovered.utilization), CURSOR_SIZE / 2); } } - private void renderSlices(RenderContext ctx, ProcessSummaryTrack.Data data, double h) { + private void renderSlices(RenderContext ctx, ProcessSummaryTrack.Data data, double w, double h) { // TODO: dedupe with CpuRenderer TimeSpan visible = state.getVisibleTime(); - Selection selected = state.getSelection(Selection.Kind.Cpu); + Selection selected = state.getSelection(Selection.Kind.Cpu); List visibleSelected = Lists.newArrayList(); int cpuCount = state.getCpuInfo().count(); double cpuH = (h - cpuCount + 1) / cpuCount; @@ -178,29 +200,33 @@ private void renderSlices(RenderContext ctx, ProcessSummaryTrack.Data data, doub } if (hoveredThread != null) { + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - (hoveredWidth + 2 * HOVER_PADDING)) { + cardX = mouseXpos - HOVER_MARGIN - hoveredWidth - 2 * HOVER_PADDING; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(mouseXpos + HOVER_MARGIN, 0, hoveredWidth + 2 * HOVER_PADDING, h); + ctx.fillRect(cardX, 0, hoveredWidth + 2 * HOVER_PADDING, h); ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoveredThread.title, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, 2, (h / 2) - 4); + ctx.drawText(Fonts.Style.Normal, hoveredThread.title, cardX + HOVER_PADDING, 2, (h / 2) - 4); if (!hoveredThread.subTitle.isEmpty()) { - ctx.drawText(Fonts.Style.Normal, hoveredThread.subTitle, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, (h / 2) + 2, (h / 2) - 4); + ctx.drawText(Fonts.Style.Normal, hoveredThread.subTitle, cardX + HOVER_PADDING, + (h / 2) + 2, (h / 2) - 4); } } } @Override - public Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - ProcessSummaryTrack.Data data = track.getData(state.toRequest(), onUiThread()); + public Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + ProcessSummaryTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null) { return Hover.NONE; } switch (data.kind) { case slice: return sliceHover(data, m, x, y, mods); - case summary: return summaryHover(data, m, x); + case summary: return summaryHover(data, m, x, mods); default: return Hover.NONE; } } @@ -231,7 +257,13 @@ private Hover sliceHover( return new Hover() { @Override public Area getRedraw() { - return new Area(x + HOVER_MARGIN, 0, hoveredWidth + 2 * HOVER_PADDING, HEIGHT); + double redrawW = HOVER_MARGIN + hoveredWidth + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + // redrawW *= 2; + } + return new Area(redrawX, 0, redrawW, HEIGHT); } @Override @@ -262,7 +294,7 @@ public boolean click() { return Hover.NONE; } - private Hover summaryHover(ProcessSummaryTrack.Data data, Fonts.TextMeasurer m, double x) { + private Hover summaryHover(ProcessSummaryTrack.Data data, Fonts.TextMeasurer m, double x, int mods) { long time = state.pxToTime(x); int bucket = (int)((time - data.request.range.start) / data.bucketSize); if (bucket < 0 || bucket >= data.utilizations.length) { @@ -278,19 +310,71 @@ private Hover summaryHover(ProcessSummaryTrack.Data data, Fonts.TextMeasurer m, data.request.range.start + hovered.bucket * data.bucketSize + data.bucketSize / 2); double dx = HOVER_PADDING + hovered.size.w + HOVER_PADDING; double dy = height; + String ids = Arrays.getOrDefault(data.concatedIds, bucket, ""); + return new Hover() { @Override public Area getRedraw() { - return new Area(mouseX - CURSOR_SIZE, -TRACK_MARGIN, CURSOR_SIZE + HOVER_MARGIN + dx, dy); + double redrawW = CURSOR_SIZE + HOVER_MARGIN + dx; + double redrawX = mouseX - CURSOR_SIZE / 2; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = mouseX + CURSOR_SIZE / 2 - redrawW; + + // If the hover card is drawn on the left side of the hover point, when moving the mouse + // from left to right, the right edge of the cursor doesn't seem to get redrawn all the + // time, this looks like a precision issue. This also happens when cursor is now on the + // right side of the hover card, and the mouse moving from right to left there seems to + // be a precision issue on the right edge of the cursor, hence extend the redraw with by + // plusing the radius of the cursor. + redrawW += CURSOR_SIZE / 2; + } + return new Area(redrawX, -TRACK_MARGIN, redrawW, dy); } @Override public void stop() { hovered = null; } + + @Override + public Cursor getCursor(Display display) { + return ids.isEmpty() ? null : display.getSystemCursor(SWT.CURSOR_HAND); + } + + @Override + public boolean click() { + if (ids.isEmpty()) { + return false; + } + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Cpu, transform(track.getSlices(ids), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + })); + } else { + state.clearSelectedThreads(); + state.setSelection(Selection.Kind.Cpu, transform(track.getSlices(ids), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + })); + } + return true; + } }; } + @Override + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { + builder.add(Selection.Kind.Cpu, computeSelection(ts)); + } + + private ListenableFuture computeSelection(TimeSpan ts) { + return transform(track.getSlices(ts), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + }); + } + private static class HoverCard { public final int bucket; public final double utilization; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/RootPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/RootPanel.java index 443c5d8282..a5285ca2f4 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/RootPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/RootPanel.java @@ -19,10 +19,14 @@ import static com.google.gapid.perfetto.views.StyleConstants.HIGHLIGHT_EDGE_NEARBY_WIDTH; import static com.google.gapid.perfetto.views.StyleConstants.LABEL_WIDTH; import static com.google.gapid.perfetto.views.StyleConstants.colors; +import static com.google.gapid.perfetto.views.StyleConstants.flag; +import static com.google.gapid.perfetto.views.StyleConstants.flagFilled; +import static com.google.gapid.perfetto.views.StyleConstants.flagGreyed; import static com.google.gapid.widgets.Widgets.createToggleToolItem; import static com.google.gapid.widgets.Widgets.exclusiveSelection; import static java.util.Arrays.stream; +import com.google.common.collect.Maps; import com.google.gapid.models.Settings; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; @@ -43,6 +47,8 @@ import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.ToolItem; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.IntConsumer; @@ -56,6 +62,8 @@ public abstract class RootPanel extends Panel.Base implements S private static final double HIGHLIGHT_BOTTOM = 30; private static final double HIGHLIGHT_CENTER = (HIGHLIGHT_TOP + HIGHLIGHT_BOTTOM) / 2; private static final double HIGHLIGHT_PADDING = 3; + private static final double FLAG_WIDTH = 17; // Width of the actual flag, not the image's pixel width + public static final double FLAGS_Y = 30; protected final Settings settings; protected final TimelinePanel timeline; @@ -70,11 +78,16 @@ public abstract class RootPanel extends Panel.Base implements S private boolean isHighlightStartHovered = false; private boolean isHighlightEndHovered = false; + protected TreeMap flags; + protected boolean flagHovered = false; + protected double flagHoverXpos; + public RootPanel(S state, Settings settings) { this.settings = settings; this.timeline = new TimelinePanel(state); this.state = state; this.showVSync = settings.ui().getPerfetto().getShowVsync(); + this.flags = Maps.newTreeMap(); state.addListener(this); } @@ -106,6 +119,36 @@ public void setSize(double w, double h) { state.setMaxScrollOffset(bottomHeight - h + topHeight); } + private SortedMap searchForFlag(double x) { + long time = state.pxToTime(x); + // If the click falls within the flag icon's boundaries, the flag is considered to be hit. + // Leave an additional two pixels to the left of the flag to get a better hit box + long rightOffset = state.deltaPxToDuration(FLAG_WIDTH); + long leftOffset = state.deltaPxToDuration(2); + + return flags.subMap(time - rightOffset, time + leftOffset); + } + + protected void searchAndRemoveFlag(double x) { + SortedMap subMap = searchForFlag(x); + if (!subMap.isEmpty()) { + subMap.clear(); + } + } + + protected void searchAndAddFlag(double x) { + SortedMap subMap = searchForFlag(x); + if (subMap.isEmpty()) { + flags.put(state.pxToTime(x), true); + } else { + toggleFlag(subMap); + } + } + + private static void toggleFlag(SortedMap subMap) { + subMap.replaceAll((k,v) -> v = !v); + } + @Override public void render(RenderContext ctx, Repainter repainter) { double topHeight = top.getPreferredHeight(); @@ -125,6 +168,8 @@ public void render(RenderContext ctx, Repainter repainter) { }); } + postMainUiRender(ctx); + TimeSpan highlight = state.getHighlight(); if (highlight != TimeSpan.ZERO) { double newClipX = Math.max(clip.x, LABEL_WIDTH); @@ -196,6 +241,7 @@ public void render(RenderContext ctx, Repainter repainter) { protected abstract void preTopUiRender(RenderContext ctx, Repainter repainter); protected abstract void preMainUiRender(RenderContext ctx, Repainter repainter); + protected abstract void postMainUiRender(RenderContext ctx); @Override public void visit(Visitor v, Area area) { @@ -358,11 +404,16 @@ protected void finishSelection(int mods) { } @Override - public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { + public Hover onMouseMove(Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + if (y >= (timeline.getPreferredHeight()/2) && y <= timeline.getPreferredHeight() && x > LABEL_WIDTH) { + return flagHover(x); + } double topHeight = top.getPreferredHeight(); - Hover result = (y < topHeight) ? top.onMouseMove(m, x, y, mods) : - bottom.onMouseMove(m, x, y - topHeight + state.getScrollOffset(), mods) - .transformed(a -> a.translate(0, topHeight - state.getScrollOffset())); + Hover result = (y < topHeight) ? top.onMouseMove(m, repainter, x, y, mods) : + bottom.onMouseMove(m, + repainter.transformed(a -> a.translate(0, topHeight - state.getScrollOffset())), + x, y - topHeight + state.getScrollOffset(), mods + ).transformed(a -> a.translate(0, topHeight - state.getScrollOffset())); if (x >= LABEL_WIDTH && y >= topHeight && result == Hover.NONE) { result = result.withClick(() -> state.resetSelections()); } @@ -382,6 +433,41 @@ public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { return result; } + private Hover flagHover(double x) { + if (searchForFlag(x - LABEL_WIDTH).isEmpty()) { + flagHovered = true; + flagHoverXpos = x; + } else { + flagHovered = false; + } + return new Panel.Hover() { + @Override + public Area getRedraw() { + TimeSpan visible = state.getVisibleTime(); + // Redraw the entire visible range + return new Area( + state.timeToPx(visible.start), 0, bottom.getPreferredHeight(), state.timeToPx(visible.end)); + } + + @Override + public boolean click() { + searchAndAddFlag(x - LABEL_WIDTH); + return true; + } + + @Override + public boolean rightClick() { + searchAndRemoveFlag(x - LABEL_WIDTH); + return true; + } + + @Override + public void stop() { + flagHovered = false; + } + }; + } + private double findHighlightFixedEnd(double sx) { double hStart = state.timeToPx(state.getHighlight().start) + LABEL_WIDTH; double hEnd = state.timeToPx(state.getHighlight().end) + LABEL_WIDTH; @@ -435,6 +521,17 @@ public boolean zoom(double x, double zoomFactor) { return state.setVisibleTime(new TimeSpan(newStart, newEnd)); } + public void updateFilter(String search) { + bottom.updateFilter(panel -> { + if (panel instanceof FilterablePanel) { + return ((FilterablePanel)panel).include(search) ? + Panel.FilterValue.Include : Panel.FilterValue.DescendOrExclude; + } + // Panels that are not filterable, should be shown if they are not groups. + return Panel.FilterValue.DescendOrInclude; + }); + } + public static class ForSystemTrace extends RootPanel { public ForSystemTrace(State.ForSystemTrace state, Settings settings) { @@ -466,6 +563,33 @@ protected void preMainUiRender(RenderContext ctx, Repainter repainter) { } } + @Override + protected void postMainUiRender(RenderContext ctx) { + // Render the Flag in the timeline panel and the vertical line in the bottom panel group + renderFlags(ctx, bottom); + } + + private void renderFlags(RenderContext ctx, Panel panel) { + flags.forEach((k,v) -> { + double x = Math.rint(LABEL_WIDTH + state.timeToPx(k)); + if (x > LABEL_WIDTH) { + if (v) { + ctx.drawIcon(flagFilled(ctx.theme), x - 5, FLAGS_Y, 0); + ctx.setForegroundColor(colors().flagLine); + ctx.drawLine(x, FLAGS_Y, x, panel.getPreferredHeight()); + } else { + ctx.drawIcon(flag(ctx.theme), x - 5, FLAGS_Y, 0); + } + } + }); + if (flagHovered) { + double x = flagHoverXpos; + ctx.drawIcon(flagGreyed(ctx.theme), x - 5, FLAGS_Y, 0); + ctx.setForegroundColor(colors().flagHover); + ctx.drawLine(x, FLAGS_Y, x, panel.getPreferredHeight()); + } + } + private void renderVSync(RenderContext ctx, Repainter repainter, Panel panel, VSync vsync) { ctx.trace("VSync", () -> { VSync.Data data = vsync.getData(state.toRequest(), diff --git a/gapic/src/main/com/google/gapid/perfetto/views/SelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/SelectionView.java index 51b0dbdb13..65ab7cfda2 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/SelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/SelectionView.java @@ -49,7 +49,10 @@ public void onSelectionChanged(Selection.MultiSelection selection) { } if (selection != null) { - selection.buildUi(this, state).requestLayout(); + Composite composite = selection.buildUi(this, state); + if (composite != null) { + composite.requestLayout(); + } } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/SliceSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/SliceSelectionView.java deleted file mode 100644 index 00a18bf429..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/views/SliceSelectionView.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ -package com.google.gapid.perfetto.views; - -import static com.google.gapid.perfetto.TimeSpan.timeToString; -import static com.google.gapid.widgets.Widgets.createBoldLabel; -import static com.google.gapid.widgets.Widgets.createComposite; -import static com.google.gapid.widgets.Widgets.createLabel; -import static com.google.gapid.widgets.Widgets.withIndents; -import static com.google.gapid.widgets.Widgets.withLayoutData; -import static com.google.gapid.widgets.Widgets.withMargin; -import static com.google.gapid.widgets.Widgets.withSpans; - -import com.google.common.collect.ImmutableMap; -import com.google.common.collect.Iterables; -import com.google.gapid.perfetto.models.ProcessInfo; -import com.google.gapid.perfetto.models.SliceTrack; -import com.google.gapid.perfetto.models.SliceTrack.RenderStageInfo; -import com.google.gapid.perfetto.models.ThreadInfo; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; - -/** - * Displays information about a selected slice. - */ -public class SliceSelectionView extends Composite { - private static final int PROPERTIES_PER_PANEL = 8; - private static final int PANEL_INDENT = 25; - - public SliceSelectionView(Composite parent, State state, SliceTrack.Slice slice) { - super(parent, SWT.NONE); - setLayout(withMargin(new GridLayout(2, false), 0, 0)); - - Composite main = withLayoutData(createComposite(this, new GridLayout(2, false)), - new GridData(SWT.LEFT, SWT.TOP, false, false)); - withLayoutData(createBoldLabel(main, "Slice:"), withSpans(new GridData(), 2, 1)); - - createLabel(main, "Time:"); - createLabel(main, timeToString(slice.time - state.getTraceTime().start)); - - createLabel(main, "Duration:"); - createLabel(main, timeToString(slice.dur)); - - ThreadInfo thread = slice.getThread(); - if (thread != null) { - ProcessInfo process = state.getProcessInfo(thread.upid); - if (process != null) { - createLabel(main, "Process:"); - createLabel(main, process.getDisplay()); - } - - createLabel(main, "Thread:"); - createLabel(main, thread.getDisplay()); - } - - if (!slice.category.isEmpty()) { - createLabel(main, "Category:"); - createLabel(main, slice.category); - } - - if (!slice.name.isEmpty()) { - createLabel(main, "Name:"); - createLabel(main, slice.name); - } - - RenderStageInfo renderStageInfo = slice.getRenderStageInfo(); - if (renderStageInfo != null) { - ImmutableMap.Builder propsBuilder = ImmutableMap.builder(); - if (renderStageInfo.frameBufferHandle != 0) { - if (renderStageInfo.frameBufferName.isEmpty()) { - propsBuilder.put("VkFrameBuffer:", String.format("0x%08X", renderStageInfo.frameBufferHandle)); - } else { - propsBuilder.put("VkFrameBuffer:", renderStageInfo.frameBufferName + " <" + String.format("0x%08X", renderStageInfo.frameBufferHandle) + ">"); - } - } - if (renderStageInfo.renderPassHandle != 0) { - if (renderStageInfo.renderPassName.isEmpty()) { - propsBuilder.put("VkRenderPass:", String.format("0x%08X", renderStageInfo.renderPassHandle)); - } else { - propsBuilder.put("VkRenderPass:", renderStageInfo.renderPassName + " <" + String.format("0x%08X", renderStageInfo.renderPassHandle) + ">"); - } - } - if (renderStageInfo.commandBufferHandle != 0) { - if (renderStageInfo.commandBufferName.isEmpty()) { - propsBuilder.put("VkCommandBuffer:", String.format("0x%08X", renderStageInfo.commandBufferHandle)); - } else { - propsBuilder.put("VkCommandBuffer:", renderStageInfo.commandBufferName + " <" + String.format("0x%08X", renderStageInfo.commandBufferHandle) + ">"); - } - } - - if (renderStageInfo.submissionId != 0) { - propsBuilder.put("SubmissionId:", Long.toString(renderStageInfo.submissionId)); - } - - ImmutableMap props = propsBuilder.build(); - if (!props.isEmpty()) { - withLayoutData(createBoldLabel(main, "Vulkan Info:"), - withSpans(new GridData(), 2, 1)); - props.forEach((key, value) -> { - createLabel(main, key); - createLabel(main, value); - }); - } - } - - if (!slice.args.isEmpty()) { - String[] keys = Iterables.toArray(slice.args.keys(), String.class); - int panels = (keys.length + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; - Composite props = withLayoutData(createComposite(this, new GridLayout(2 * panels, false)), - withIndents(new GridData(SWT.LEFT, SWT.TOP, false, false), PANEL_INDENT, 0)); - withLayoutData(createBoldLabel(props, "Properties:"), - withSpans(new GridData(), 2 * panels, 1)); - - for (int i = 0; i < keys.length && i < PROPERTIES_PER_PANEL; i++) { - int cols = (keys.length - i + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; - for (int c = 0; c < cols; c++) { - withLayoutData(createLabel(props, keys[i + c * PROPERTIES_PER_PANEL] + ":"), - withIndents(new GridData(), (c == 0) ? 0 : PANEL_INDENT, 0)); - createLabel(props, String.valueOf(slice.args.get(keys[i + c * PROPERTIES_PER_PANEL]))); - } - if (cols != panels) { - withLayoutData(createLabel(props, ""), withSpans(new GridData(), 2 * (panels - cols), 1)); - } - } - } - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/SlicesSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/SlicesSelectionView.java index 247885504f..98f8e22cab 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/SlicesSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/SlicesSelectionView.java @@ -16,33 +16,175 @@ package com.google.gapid.perfetto.views; import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createBoldLabel; +import static com.google.gapid.widgets.Widgets.createComposite; +import static com.google.gapid.widgets.Widgets.createLabel; +import static com.google.gapid.widgets.Widgets.createSelectableLabel; import static com.google.gapid.widgets.Widgets.createTreeColumn; import static com.google.gapid.widgets.Widgets.createTreeViewer; import static com.google.gapid.widgets.Widgets.packColumns; +import static com.google.gapid.widgets.Widgets.withIndents; +import static com.google.gapid.widgets.Widgets.withLayoutData; +import static com.google.gapid.widgets.Widgets.withMargin; +import static com.google.gapid.widgets.Widgets.withSpans; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Iterables; +import com.google.gapid.perfetto.models.ProcessInfo; import com.google.gapid.perfetto.models.SliceTrack; +import com.google.gapid.perfetto.models.SliceTrack.GpuSlices; +import com.google.gapid.perfetto.models.SliceTrack.RenderStageInfo; +import com.google.gapid.perfetto.models.SliceTrack.ThreadSlices; +import com.google.gapid.perfetto.models.ThreadInfo; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; /** * Displays information about a list of selected slices. */ public class SlicesSelectionView extends Composite { - public SlicesSelectionView(Composite parent, SliceTrack.Slices sel) { + private static final int PROPERTIES_PER_PANEL = 9; + private static final int PANEL_INDENT = 25; + + public SlicesSelectionView(Composite parent, State state, SliceTrack.Slices slices) { super(parent, SWT.NONE); + if (slices.getCount() == 1) { + setSingleSliceView(state, slices); + } else if (slices.getCount() > 1) { + setMultiSlicesView(slices); + } + } + + private void setSingleSliceView(State state, SliceTrack.Slices slice) { + setLayout(withMargin(new GridLayout(2, false), 0, 0)); + + Composite main = withLayoutData(createComposite(this, new GridLayout(2, false)), + new GridData(SWT.LEFT, SWT.TOP, false, false)); + withLayoutData(createBoldLabel(main, "Slice:"), withSpans(new GridData(), 2, 1)); + + createLabel(main, "Time:"); + createLabel(main, timeToString(slice.times.get(0) - state.getTraceTime().start)); + + createLabel(main, "Duration:"); + createLabel(main, timeToString(slice.durs.get(0))); + + ThreadInfo thread = ThreadInfo.EMPTY; + if (slice instanceof ThreadSlices) { + thread = ((ThreadSlices)slice).getThreadAt(0); + } + if (thread != null && thread != ThreadInfo.EMPTY) { + ProcessInfo process = state.getProcessInfo(thread.upid); + if (process != null) { + createLabel(main, "Process:"); + createLabel(main, process.getDisplay()); + } + + createLabel(main, "Thread:"); + createLabel(main, thread.getDisplay()); + } + + if (!slice.categories.get(0).isEmpty()) { + createLabel(main, "Category:"); + createLabel(main, slice.categories.get(0)); + } + + if (!slice.names.get(0).isEmpty()) { + createLabel(main, "Name:"); + createLabel(main, slice.names.get(0)); + } + + RenderStageInfo renderStageInfo = RenderStageInfo.EMPTY; + if (slice instanceof GpuSlices) { + renderStageInfo = ((GpuSlices)slice).getRenderStageInfoAt(0); + } + ImmutableMap.Builder propsBuilder = ImmutableMap.builder(); + if (renderStageInfo != null && renderStageInfo != RenderStageInfo.EMPTY) { + ImmutableMap.Builder vkPropsBuilder = ImmutableMap.builder(); + if (renderStageInfo.frameBufferHandle != 0) { + if (renderStageInfo.frameBufferName.isEmpty()) { + propsBuilder.put("Render Target:", String.format("0x%08X", renderStageInfo.frameBufferHandle)); + } else { + propsBuilder.put("Render Target:", renderStageInfo.frameBufferName + " <" + String.format("0x%08X", renderStageInfo.frameBufferHandle) + ">"); + } + } + if (renderStageInfo.renderPassHandle != 0) { + if (renderStageInfo.renderPassName.isEmpty()) { + vkPropsBuilder.put("VkRenderPass:", String.format("0x%08X", renderStageInfo.renderPassHandle)); + } else { + vkPropsBuilder.put("VkRenderPass:", renderStageInfo.renderPassName + " <" + String.format("0x%08X", renderStageInfo.renderPassHandle) + ">"); + } + } + if (renderStageInfo.commandBufferHandle != 0) { + if (renderStageInfo.commandBufferName.isEmpty()) { + vkPropsBuilder.put("VkCommandBuffer:", String.format("0x%08X", renderStageInfo.commandBufferHandle)); + } else { + vkPropsBuilder.put("VkCommandBuffer:", renderStageInfo.commandBufferName + " <" + String.format("0x%08X", renderStageInfo.commandBufferHandle) + ">"); + } + } + + if (renderStageInfo.submissionId != 0) { + propsBuilder.put("SubmissionId:", Long.toString(renderStageInfo.submissionId)); + } + ImmutableMap props = propsBuilder.build(); + if (!props.isEmpty()) { + withLayoutData(createBoldLabel(main, "General Info:"), + withSpans(new GridData(), 2, 1)); + props.forEach((key, value) -> { + createSelectableLabel(main, key); + createSelectableLabel(main, value); + }); + } + ImmutableMap vkProps = vkPropsBuilder.build(); + if (!vkProps.isEmpty()) { + withLayoutData(createBoldLabel(main, "Vulkan Info:"), + withSpans(new GridData(), 2, 1)); + vkProps.forEach((key, value) -> { + createSelectableLabel(main, key); + createSelectableLabel(main, value); + }); + } + } + + if (!slice.argsets.get(0).isEmpty()) { + String[] keys = Iterables.toArray(slice.argsets.get(0).keys(), String.class); + int panels = (keys.length + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; + Composite props = withLayoutData(createComposite(this, new GridLayout(2 * panels, false)), + withIndents(new GridData(SWT.LEFT, SWT.TOP, false, false), PANEL_INDENT, 0)); + withLayoutData(createBoldLabel(props, "Properties:"), + withSpans(new GridData(), 2 * panels, 1)); + + for (int i = 0; i < keys.length && i < PROPERTIES_PER_PANEL; i++) { + int cols = (keys.length - i + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; + for (int c = 0; c < cols; c++) { + withLayoutData(createSelectableLabel(props, keys[i + c * PROPERTIES_PER_PANEL] + ":"), + withIndents(new GridData(), (c == 0) ? 0 : PANEL_INDENT, 0)); + createSelectableLabel(props, String.valueOf(slice.argsets.get(0).get(keys[i + c * PROPERTIES_PER_PANEL]))); + } + if (cols != panels) { + withLayoutData(createLabel(props, ""), withSpans(new GridData(), 2 * (panels - cols), 1)); + } + } + } + } + + private void setMultiSlicesView(SliceTrack.Slices slices) { setLayout(new FillLayout()); + SliceTrack.Node[] nodes = SliceTrack.organizeSlicesToNodes(slices); + TreeViewer viewer = createTreeViewer(this, SWT.NONE); viewer.getTree().setHeaderVisible(true); viewer.setContentProvider(new ITreeContentProvider() { @Override public Object[] getElements(Object inputElement) { - return sel.nodes.toArray(); + return nodes; } @Override @@ -68,11 +210,11 @@ public Object[] getChildren(Object element) { createTreeColumn(viewer, "Count", e -> Integer.toString(n(e).count)); createTreeColumn(viewer, "Avg Wall Time", e -> timeToString(n(e).dur / n(e).count)); createTreeColumn(viewer, "Avg Self TIme", e -> timeToString(n(e).self / n(e).count)); - viewer.setInput(sel); + viewer.setInput(nodes); packColumns(viewer.getTree()); } - protected static SliceTrack.Node n(Object o) { + private static SliceTrack.Node n(Object o) { return (SliceTrack.Node)o; } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/State.java b/gapic/src/main/com/google/gapid/perfetto/views/State.java index c71a042c56..9f384003a5 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/State.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/State.java @@ -130,7 +130,7 @@ public Selection.MultiSelection getSelection() { return selection; } - public Selection getSelection(Selection.Kind type) { + public > T getSelection(Selection.Kind type) { if (selection == null) { return Selection.emptySelection(); } else { @@ -160,6 +160,10 @@ public void setWidth(double width) { } } + public double getWidth() { + return width; + } + public Track.DataRequest toRequest() { return new Track.DataRequest(visibleTime, resolution); } @@ -213,7 +217,7 @@ public boolean resetSelections() { return hasDeselection; } - public void addSelection(Selection.Kind type, ListenableFuture> futureSel) { + public void addSelection(Selection.Kind type, ListenableFuture> futureSel) { int myId = lastSelectionUpdateId.incrementAndGet(); thenOnUiThread(futureSel, newSelection -> { if (lastSelectionUpdateId.get() == myId) { @@ -222,7 +226,7 @@ public void addSelection(Selection.Kind type, ListenableFuture void addSelection(Selection.Kind type, Selection newSel) { + public void addSelection(Selection.Kind type, Selection newSel) { if (selection == null) { setSelection(type, newSel); } else { @@ -245,7 +249,7 @@ public void addSelection(ListenableFuture futureSel) { }); } - public void setSelection(Selection.Kind type, ListenableFuture> futureSel) { + public void setSelection(Selection.Kind type, ListenableFuture> futureSel) { int myId = lastSelectionUpdateId.incrementAndGet(); thenOnUiThread(futureSel, newSelection -> { if (lastSelectionUpdateId.get() == myId) { @@ -254,7 +258,7 @@ public void setSelection(Selection.Kind type, ListenableFuture void setSelection(Selection.Kind type, Selection selection) { + public void setSelection(Selection.Kind type, Selection selection) { setSelection(new Selection.MultiSelection(type, selection)); } @@ -323,9 +327,9 @@ public ListenableFuture onUiThread(Runnable run) { private void update() { nanosPerPx = visibleTime.getDuration() / width; - if (nanosPerPx <= 0) { - nanosPerPx = 0; - resolution = 0; + if (width <= 0 || nanosPerPx <= 0) { + nanosPerPx = 1; + resolution = 1; } else { resolution = 1l << DoubleMath.log2(nanosPerPx, RoundingMode.FLOOR); } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java b/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java index 9b4fbd7afe..dbac4950fd 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/StyleConstants.java @@ -38,7 +38,8 @@ public class StyleConstants { public static final double LABEL_TOGGLE_X = LABEL_PIN_X - LABEL_ICON_SIZE; public static final double TRACK_MARGIN = 4; public static final double DEFAULT_COUNTER_TRACK_HEIGHT = 45; - public static final double PROCESS_COUNTER_TRACK_HIGHT = 30; + public static final double PROCESS_COUNTER_TRACK_HEIGHT = 30; + public static final double POWER_RAIL_COUNTER_TRACK_HEIGHT = 30; public static final double HIGHLIGHT_EDGE_NEARBY_WIDTH = 10; public static final double SELECTION_THRESHOLD = 0.333; public static final double ZOOM_FACTOR_SCALE = 0.05; @@ -51,6 +52,10 @@ public class StyleConstants { public static final double KB_ZOOM_SLOW = 2 * ZOOM_FACTOR_SCALE; public static final double KB_ZOOM_FAST = 3 * ZOOM_FACTOR_SCALE; + // Touchpad handling constants. + public static final int TP_PAN_SLOW = 30; + public static final int TP_PAN_FAST = 60; + public static class Colors { public final RGBA background; public final RGBA titleBackground; @@ -67,6 +72,8 @@ public static class Colors { public final RGBA cpuFreqIdle; public final RGBA timelineRuler; public final RGBA vsyncBackground; + public final RGBA flagLine; + public final RGBA flagHover; public final RGBA textMain; public final RGBA textAlt; @@ -86,6 +93,8 @@ public Colors(RGBA background, RGBA cpuFreqIdle, RGBA timelineRuler, RGBA vsyncBackground, + RGBA flagLine, + RGBA flagHover, RGBA textMain, RGBA textAlt) { this.background = background; @@ -103,6 +112,8 @@ public Colors(RGBA background, this.cpuFreqIdle = cpuFreqIdle; this.timelineRuler = timelineRuler; this.vsyncBackground = vsyncBackground; + this.flagLine = flagLine; + this.flagHover = flagHover; this.textMain = textMain; this.textAlt = textAlt; } @@ -122,6 +133,8 @@ public Colors(RGBA background, private static final RGBA LIGHT_CPU_FREQ_IDLE = rgb(0xf0, 0xf0, 0xf0); private static final RGBA LIGHT_TIMELINE_RULER = rgb(0x99, 0x99, 0x99); private static final RGBA LIGHT_VSYNC_BACKGROUND = rgb(0xf5, 0xf5, 0xf5); + private static final RGBA LIGHT_FLAG_LINE = rgb(0, 0, 0); + private static final RGBA LIGHT_FLAG_HOVER = rgb(0x80, 0x80, 0x80); private static final RGBA LIGHT_TEXT_MAIN = rgb(0x32, 0x34, 0x35); private static final RGBA LIGHT_TEXT_ALT = rgb(101, 102, 104); @@ -143,6 +156,8 @@ public static Colors light() { LIGHT_CPU_FREQ_IDLE, LIGHT_TIMELINE_RULER, LIGHT_VSYNC_BACKGROUND, + LIGHT_FLAG_LINE, + LIGHT_FLAG_HOVER, LIGHT_TEXT_MAIN, LIGHT_TEXT_ALT); } @@ -162,6 +177,8 @@ public static Colors light() { private static final RGBA DARK_CPU_FREQ_IDLE = rgb(0x55, 0x55, 0x55); private static final RGBA DARK_TIMELINE_RULER = rgb(0x99, 0x99, 0x99); private static final RGBA DARK_VSYNC_BACKGROUND = rgb(0x24, 0x24, 0x24); + private static final RGBA DARK_FLAG_LINE = rgb(0xff, 0xff, 0xff); + private static final RGBA DARK_FLAG_HOVER = rgb(0x80, 0x80, 0x80); private static final RGBA DARK_TEXT_MAIN = rgb(0xf1, 0xf1, 0xf8); private static final RGBA DARK_TEXT_ALT = rgb(0xdd, 0xdd, 0xdd); @@ -183,6 +200,8 @@ public static Colors dark() { DARK_CPU_FREQ_IDLE, DARK_TIMELINE_RULER, DARK_VSYNC_BACKGROUND, + DARK_FLAG_LINE, + DARK_FLAG_HOVER, DARK_TEXT_MAIN, DARK_TEXT_ALT); } @@ -254,6 +273,26 @@ public static Gradient memoryBuffersGradient() { return isDark ? Gradients.DARK[14] : Gradients.LIGHT[14]; } + public static Gradient memoryRssFileGradient() { + // See Gradients.Colors for explanation of magic constants. + return isDark ? Gradients.DARK[13] : Gradients.LIGHT[16]; + } + + public static Gradient memoryRssAnonGradient() { + // See Gradients.Colors for explanation of magic constants. + return isDark ? Gradients.DARK[14] : Gradients.LIGHT[13]; + } + + public static Gradient memoryRssSharedGradient() { + // See Gradients.Colors for explanation of magic constants. + return isDark ? Gradients.DARK[16] : Gradients.LIGHT[14]; + } + + public static Gradient memorySwapGradient() { + // See Gradients.Colors for explanation of magic constants. + return isDark ? Gradients.DARK[2] : Gradients.LIGHT[2]; + } + public static boolean isLight() { return !isDark; } @@ -303,6 +342,18 @@ public static Image pinInactive(Theme theme) { return isDark ? theme.pinInactiveDark() : theme.pinInactiveLight(); } + public static Image flag(Theme theme) { + return isDark ? theme.flagDark() : theme.flagLight(); + } + + public static Image flagFilled(Theme theme) { + return isDark ? theme.flagFilledDark() : theme.flagFilledLight(); + } + + public static Image flagGreyed(Theme theme) { + return theme.flagGreyed(); + } + public static class Gradient { private static final float HIGH_TARGET = 0.9f; private static final float LOW_TARGET = 0.2f; @@ -357,6 +408,11 @@ public void applyBaseAndBorder(RenderContext ctx) { ctx.setForegroundColor(border); ctx.setBackgroundColor(base); } + + /** Sets stroke to border. **/ + public void applyBorder(RenderContext ctx) { + ctx.setForegroundColor(border); + } } private static class Gradients { @@ -364,7 +420,7 @@ private static class Gradients { private static final float[][] COLORS = { { 15.38f, 0.2633f, 0.6000f }, // Brown { 20.15f, 0.8954f, 0.7000f }, // Orange // blocked OK, battery out - { 21.92f, 0.7626f, 0.4294f }, // Dark Orange // blocked warn + { 21.92f, 0.7626f, 0.4294f }, // Dark Orange // blocked warn, swap { 36.22f, 0.7312f, 0.5000f }, // Light Brown { 42.19f, 0.4412f, 0.7490f }, // Tan { 50.00f, 0.5822f, 0.5844f }, // Gold @@ -375,10 +431,10 @@ private static class Gradients { { 130.91f, 0.6548f, 0.6706f }, // Green // running, battery in { 171.02f, 0.5787f, 0.5598f }, // Turquoise { 172.36f, 0.7432f, 0.2902f }, // Teal - { 198.40f, 1.0000f, 0.4157f }, // Pacific Blue // runnable, mem used - { 200.22f, 0.9787f, 0.8157f }, // Light Blue // main, mem buf/cache + { 198.40f, 1.0000f, 0.4157f }, // Pacific Blue // runnable, mem used, rss anon + { 200.22f, 0.9787f, 0.8157f }, // Light Blue // main, mem buf/cache, rss shared { 201.95f, 0.2455f, 0.6725f }, // Grey // sleeping - { 214.85f, 1.0000f, 0.5510f }, // Vivid Blue + { 214.85f, 1.0000f, 0.5510f }, // Vivid Blue // rss file { 217.06f, 0.5000f, 0.4000f }, // Indigo { 261.54f, 0.5065f, 0.6980f }, // Light Purple { 262.30f, 0.6981f, 0.5843f }, // Purple diff --git a/gapic/src/main/com/google/gapid/perfetto/views/ThreadPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/ThreadPanel.java index 5367bd99a0..340b3a0d0c 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/ThreadPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/ThreadPanel.java @@ -22,20 +22,18 @@ import static com.google.gapid.util.MoreFutures.transform; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.ThreadState; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; -import com.google.gapid.perfetto.models.CpuTrack; +import com.google.gapid.perfetto.models.CpuTrack.Slices; import com.google.gapid.perfetto.models.ProcessInfo; import com.google.gapid.perfetto.models.Selection; -import com.google.gapid.perfetto.models.Selection.CombiningBuilder; import com.google.gapid.perfetto.models.SliceTrack; -import com.google.gapid.perfetto.models.SliceTrack.Slice; import com.google.gapid.perfetto.models.ThreadTrack; -import com.google.gapid.perfetto.models.ThreadTrack.StateSlice; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Cursor; @@ -75,8 +73,8 @@ public ThreadPanel copy() { return new ThreadPanel(state, track, expanded); } - public void setCollapsed(boolean collapsed) { - this.expanded = !collapsed; + public void setExpanded(boolean expanded) { + this.expanded = expanded; } @Override @@ -109,9 +107,9 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } TimeSpan visible = state.getVisibleTime(); - Selection selectedCpu = state.getSelection(Selection.Kind.Cpu); - Selection selectedThreadState = state.getSelection(Selection.Kind.ThreadState); - Selection selectedThread = state.getSelection(Selection.Kind.Thread); + Selection selectedCpu = state.getSelection(Selection.Kind.Cpu); + Selection selectedThreadState = state.getSelection(Selection.Kind.ThreadState); + Selection selectedThread = state.getSelection(Selection.Kind.Thread); List visibleSelected = Lists.newArrayList(); boolean merging = false; @@ -178,9 +176,8 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } } - if (selectedCpu.contains(data.schedIds[i]) - || selectedThreadState.contains(new StateSlice.Key(data.schedStarts[i], - data.schedEnds[i] - data.schedStarts[i], track.getThread().utid))) { + if (data.schedIds[i] != 0 && selectedCpu.contains(data.schedIds[i]) + || selectedThreadState.contains(data.ids[i])) { visibleSelected.add( new Highlight(data.schedStates[i].color.get().border, rectStart, 0, rectWidth)); } @@ -192,10 +189,12 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double if (expanded) { SliceTrack.Data slices = data.slices; + String[] concatedIds = slices.getExtraStrings("concatedIds"); for (int i = 0; i < slices.starts.length; i++) { long tStart = slices.starts[i]; long tEnd = slices.ends[i]; int depth = slices.depths[i]; + long id = slices.ids[i]; //String cat = data.categories[i]; String title = slices.titles[i]; if (tEnd <= visible.start || tStart >= visible.end) { @@ -209,9 +208,17 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double color.applyBase(ctx); ctx.fillRect(rectStart, y, rectWidth, SLICE_HEIGHT); - if (selectedThread.contains(new Slice.Key(tStart, tEnd - tStart, depth))) { + if (selectedThread.contains(id)) { // Unquantized track. visibleSelected.add(new Highlight(color.border, rectStart, y, rectWidth)); } + if (i < concatedIds.length) { // Quantized track. + for (String cId : concatedIds[i].split(",")) { + if (selectedThread.contains(Long.parseLong(cId))) { + visibleSelected.add(new Highlight(color.border, rectStart, y, rectWidth)); + break; + } + } + } // Don't render text when we have less than 7px to play with. if (rectWidth < 7) { @@ -231,17 +238,20 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } if (hoveredTitle != null) { + double cardW = hoveredSize.w + 2 * HOVER_PADDING; + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - HOVER_MARGIN - cardW; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect( - mouseXpos + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + ctx.fillRect(cardX, mouseYpos, cardW, hoveredSize.h); ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoveredTitle, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, mouseYpos + HOVER_PADDING / 2); + ctx.drawText(Fonts.Style.Normal, hoveredTitle, cardX + HOVER_PADDING, + mouseYpos + HOVER_PADDING / 2); if (!hoveredCategory.isEmpty()) { ctx.setForegroundColor(colors().textAlt); - ctx.drawText(Fonts.Style.Normal, hoveredCategory, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, + ctx.drawText(Fonts.Style.Normal, hoveredCategory, cardX + HOVER_PADDING, mouseYpos + hoveredSize.h / 2, hoveredSize.h / 2); } } @@ -249,8 +259,9 @@ public void renderTrack(RenderContext ctx, Repainter repainter, double w, double } @Override - protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { - ThreadTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + ThreadTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null) { return Hover.NONE; } @@ -274,8 +285,12 @@ protected Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int m return new Hover() { @Override public Area getRedraw() { - return new Area( - x + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + double redrawW = HOVER_MARGIN + hoveredSize.w + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, mouseYpos, redrawW, hoveredSize.h); } @Override @@ -290,6 +305,17 @@ public void stop() { @Override public boolean click() { + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.ThreadState, + new ThreadTrack.StateSlices(data.ids[index], data.schedIds[index], + data.schedStarts[index], data.schedEnds[index] - data.schedStarts[index], + track.getThread().utid, data.schedStates[index])); + } else { + state.setSelection(Selection.Kind.ThreadState, + new ThreadTrack.StateSlices(data.ids[index], data.schedIds[index], + data.schedStarts[index], data.schedEnds[index] - data.schedStarts[index], + track.getThread().utid, data.schedStates[index])); + } if (data.schedIds[index] != 0) { if ((mods & SWT.MOD1) == SWT.MOD1) { state.addSelection(Selection.Kind.Cpu, track.getCpuSlice(data.schedIds[index])); @@ -298,18 +324,6 @@ public boolean click() { state.setSelection(Selection.Kind.Cpu, track.getCpuSlice(data.schedIds[index])); state.setSelectedThread(state.getThreadInfo(track.getThread().utid)); } - } else { - if ((mods & SWT.MOD1) == SWT.MOD1) { - state.addSelection(Selection.Kind.ThreadState, - new ThreadTrack.StateSlice(data.schedStarts[index], - data.schedEnds[index] - data.schedStarts[index], track.getThread().utid, - data.schedStates[index])); - } else { - state.setSelection(Selection.Kind.ThreadState, - new ThreadTrack.StateSlice(data.schedStarts[index], - data.schedEnds[index] - data.schedStarts[index], track.getThread().utid, - data.schedStates[index])); - } } return true; } @@ -337,12 +351,18 @@ public boolean click() { mouseYpos = Math.max(0, Math.min(mouseYpos - (hoveredSize.h - SLICE_HEIGHT) / 2, (1 + track.getThread().maxDepth) * SLICE_HEIGHT - hoveredSize.h)); long id = slices.ids[i]; + String concatedId = i < slices.getExtraStrings("concatedIds").length ? + slices.getExtraStrings("concatedIds")[i] : ""; return new Hover() { @Override public Area getRedraw() { - return new Area( - x + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + double redrawW = HOVER_MARGIN + hoveredSize.w + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, mouseYpos, redrawW, hoveredSize.h); } @Override @@ -352,20 +372,27 @@ public void stop() { @Override public Cursor getCursor(Display display) { - return (id < 0) ? null : display.getSystemCursor(SWT.CURSOR_HAND); + return (id < 0 && concatedId.isEmpty()) ? null : display.getSystemCursor(SWT.CURSOR_HAND); } @Override public boolean click() { - if (id < 0) { - return false; - } - if ((mods & SWT.MOD1) == SWT.MOD1) { - state.addSelection(Selection.Kind.Thread, track.getSlice(id)); - } else { - state.setSelection(Selection.Kind.Thread, track.getSlice(id)); + if (id > 0) { // Track data with no quantization. + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Thread, track.getSlice(id)); + } else { + state.setSelection(Selection.Kind.Thread, track.getSlice(id)); + } + return true; + } else if (!concatedId.isEmpty()) { // Track data with quantization. + if ((mods & SWT.MOD1) == SWT.MOD1) { + state.addSelection(Selection.Kind.Thread, track.getSlices(concatedId)); + } else { + state.setSelection(Selection.Kind.Thread, track.getSlices(concatedId)); + } + return true; } - return true; + return false; } }; } @@ -375,7 +402,7 @@ public boolean click() { } @Override - public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { int startDepth = (int)(area.y / SLICE_HEIGHT); int endDepth = (int)((area.y + area.h) / SLICE_HEIGHT); if (startDepth == endDepth && area.h / SLICE_HEIGHT < SELECTION_THRESHOLD) { @@ -392,10 +419,8 @@ public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { } if (startDepth == 0) { - builder.add(Selection.Kind.ThreadState, - transform(track.getStates(ts), ThreadTrack.StateSlicesBuilder::new)); - builder.add(Selection.Kind.Cpu, transform( - track.getCpuSlices(ts), r -> new CpuTrack.SlicesBuilder(r))); + builder.add(Selection.Kind.ThreadState, track.getStates(ts)); + builder.add(Selection.Kind.Cpu, computeSelection(ts)); } startDepth = Math.max(0, startDepth - 1); @@ -404,11 +429,17 @@ public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { if (endDepth >= track.getThread().maxDepth) { endDepth = Integer.MAX_VALUE; } - builder.add(Selection.Kind.Thread, - transform(track.getSlices(ts, startDepth, endDepth), SliceTrack.SlicesBuilder::new)); + builder.add(Selection.Kind.Thread, track.getSlices(ts, startDepth, endDepth)); } } + private ListenableFuture computeSelection(TimeSpan ts) { + return transform(track.getCpuSlices(ts), slices -> { + slices.utids.forEach(utid -> state.addSelectedThread(state.getThreadInfo(utid))); + return slices; + }); + } + private static class Highlight { public final RGBA color; public final double x, y, w; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSliceSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSliceSelectionView.java deleted file mode 100644 index 0b65a2ea61..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSliceSelectionView.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (C) 2019 Google Inc. - * - * 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. - */ -package com.google.gapid.perfetto.views; - -import static com.google.gapid.perfetto.TimeSpan.timeToString; -import static com.google.gapid.widgets.Widgets.createBoldLabel; -import static com.google.gapid.widgets.Widgets.createLabel; -import static com.google.gapid.widgets.Widgets.withLayoutData; -import static com.google.gapid.widgets.Widgets.withSpans; - -import com.google.gapid.perfetto.models.ProcessInfo; -import com.google.gapid.perfetto.models.ThreadInfo; -import com.google.gapid.perfetto.models.ThreadTrack; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; - -/** - * Displays information about a selected thread state slice. - */ -public class ThreadStateSliceSelectionView extends Composite { - public ThreadStateSliceSelectionView( - Composite parent, State state, ThreadTrack.StateSlice slice) { - super(parent, SWT.NONE); - setLayout(new GridLayout(2, false)); - - withLayoutData(createBoldLabel(this, "Slice:"), withSpans(new GridData(), 2, 1)); - - createLabel(this, "Time:"); - createLabel(this, timeToString(slice.time - state.getTraceTime().start)); - - createLabel(this, "Duration:"); - createLabel(this, timeToString(slice.dur)); - - ThreadInfo thread = state.getThreadInfo(slice.utid); - if (thread != null) { - ProcessInfo process = state.getProcessInfo(thread.upid); - if (process != null) { - createLabel(this, "Process:"); - createLabel(this, process.getDisplay()); - } - - createLabel(this, "Thread:"); - createLabel(this, thread.getDisplay()); - } - - createLabel(this, "State:"); - createLabel(this, slice.state.label); - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSlicesSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSlicesSelectionView.java index 85726f9694..129556f83b 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSlicesSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/ThreadStateSlicesSelectionView.java @@ -16,10 +16,16 @@ package com.google.gapid.perfetto.views; import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createBoldLabel; +import static com.google.gapid.widgets.Widgets.createLabel; import static com.google.gapid.widgets.Widgets.createTableColumn; import static com.google.gapid.widgets.Widgets.createTableViewer; import static com.google.gapid.widgets.Widgets.packColumns; +import static com.google.gapid.widgets.Widgets.withLayoutData; +import static com.google.gapid.widgets.Widgets.withSpans; +import com.google.gapid.perfetto.models.ProcessInfo; +import com.google.gapid.perfetto.models.ThreadInfo; import com.google.gapid.perfetto.models.ThreadTrack; import org.eclipse.jface.viewers.IStructuredContentProvider; @@ -27,6 +33,8 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; /** @@ -34,24 +42,60 @@ */ public class ThreadStateSlicesSelectionView extends Composite { public ThreadStateSlicesSelectionView( - Composite parent, ThreadTrack.StateSlices slices) { + Composite parent, State state, ThreadTrack.StateSlices slices) { super(parent, SWT.NONE); + if (slices.getCount() == 1) { + setSingleSliceView(state, slices); + } else if (slices.getCount() > 1) { + setMultiSlicesView(slices); + } + } + + private void setSingleSliceView(State state, ThreadTrack.StateSlices slice) { + setLayout(new GridLayout(2, false)); + withLayoutData(createBoldLabel(this, "Slice:"), withSpans(new GridData(), 2, 1)); + + createLabel(this, "Time:"); + createLabel(this, timeToString(slice.times.get(0) - state.getTraceTime().start)); + + createLabel(this, "Duration:"); + createLabel(this, timeToString(slice.durs.get(0))); + + ThreadInfo thread = state.getThreadInfo(slice.utids.get(0)); + if (thread != null) { + ProcessInfo process = state.getProcessInfo(thread.upid); + if (process != null) { + createLabel(this, "Process:"); + createLabel(this, process.getDisplay()); + } + + createLabel(this, "Thread:"); + createLabel(this, thread.getDisplay()); + } + + createLabel(this, "State:"); + createLabel(this, slice.states.get(0).label); + } + + private void setMultiSlicesView(ThreadTrack.StateSlices slices) { setLayout(new FillLayout()); + ThreadTrack.Entry[] entries = ThreadTrack.organizeSlicesToEntry(slices); + TableViewer viewer = createTableViewer(this, SWT.NONE); viewer.setContentProvider(new IStructuredContentProvider() { @Override public Object[] getElements(Object inputElement) { - return slices.entries.toArray(); + return entries; } }); viewer.setLabelProvider(new LabelProvider()); createTableColumn(viewer, "State", - e -> ((ThreadTrack.StateSlices.Entry)e).state.label); + e -> ((ThreadTrack.Entry)e).state.label); createTableColumn(viewer, "Duration", - e -> timeToString(((ThreadTrack.StateSlices.Entry)e).totalDur)); - viewer.setInput(slices); + e -> timeToString(((ThreadTrack.Entry)e).totalDur)); + viewer.setInput(entries); packColumns(viewer.getTable()); } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TimelinePanel.java b/gapic/src/main/com/google/gapid/perfetto/views/TimelinePanel.java index a5665222ef..70900ddec5 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/TimelinePanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/TimelinePanel.java @@ -51,9 +51,6 @@ public void render(RenderContext ctx, Repainter repainter) { ctx.trace("TimelinePanel", () -> { TimeSpan visible = state.getVisibleTime(); TimeSpan trace = state.getTraceTime(); - if (visible.getDuration() == 0) { - return; - } // TODO: this should be part of the state - dedupe with below. long step = getGridStepSize( @@ -121,7 +118,7 @@ public static void drawGridLines( ctx.setForegroundColor(colors().gridline); ctx.path(path -> { - for (long sec = start; step > 0 && sec < visible.end; sec += step) { + for (long sec = start; sec < visible.end; sec += step) { double xPos = Math.floor(state.timeToPx(sec)); if (xPos < 0) { continue; @@ -154,7 +151,7 @@ private static long getGridStepSize(long range, double desiredSteps) { result = step; } } - return Math.round(result); + return Math.max(1, Math.round(result)); } private static String timeToString(long ns, long resolution) { diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TraceComposite.java b/gapic/src/main/com/google/gapid/perfetto/views/TraceComposite.java index 97bcdba550..f9a8bafcb4 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/TraceComposite.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/TraceComposite.java @@ -21,14 +21,19 @@ import static com.google.gapid.perfetto.views.StyleConstants.KB_PAN_SLOW; import static com.google.gapid.perfetto.views.StyleConstants.KB_ZOOM_FAST; import static com.google.gapid.perfetto.views.StyleConstants.KB_ZOOM_SLOW; +import static com.google.gapid.perfetto.views.StyleConstants.TP_PAN_FAST; +import static com.google.gapid.perfetto.views.StyleConstants.TP_PAN_SLOW; import static com.google.gapid.perfetto.views.StyleConstants.ZOOM_FACTOR_SCALE; +import static com.google.gapid.perfetto.views.TraceMetadataDialog.showMetadata; import static com.google.gapid.widgets.Widgets.createButtonWithImage; import static com.google.gapid.widgets.Widgets.createLabel; import static com.google.gapid.widgets.Widgets.scheduleIfNotDisposed; import static com.google.gapid.widgets.Widgets.withLayoutData; import static com.google.gapid.widgets.Widgets.withMargin; +import static com.google.gapid.widgets.Widgets.withSizeHints; import com.google.gapid.models.Analytics; +import com.google.gapid.models.Perfetto; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.PanelCanvas; @@ -44,6 +49,7 @@ import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Text; import org.eclipse.swt.widgets.ToolBar; import java.util.function.Consumer; @@ -57,14 +63,16 @@ public abstract class TraceComposite extends Composite implemen private final RootPanel rootPanel; private final PanelCanvas canvas; - public TraceComposite(Composite parent, Analytics analytics, Theme theme) { + public TraceComposite( + Composite parent, Analytics analytics, Perfetto perfetto, Theme theme, boolean fullView) { super(parent, SWT.NONE); this.state = createState(); this.rootPanel = createRootPanel(); state.addListener(this); setLayout(withMargin(new GridLayout(1, false), 0, 0)); - TopBar topBar = withLayoutData(new TopBar(this, analytics, theme), + TopBar topBar = withLayoutData( + new TopBar(this, analytics, perfetto, theme, fullView ? this::updateFilter : null), new GridData(SWT.FILL, SWT.TOP, true, false)); canvas = withLayoutData(new PanelCanvas(this, SWT.H_SCROLL | SWT.V_SCROLL, theme, rootPanel), new GridData(SWT.FILL, SWT.FILL, true, true)); @@ -82,9 +90,17 @@ public TraceComposite(Composite parent, Analytics analytics, Theme theme) { } }); canvas.addListener(SWT.MouseHorizontalWheel, e -> { - if ((e.stateMask & SWT.MODIFIER_MASK) == SWT.MOD1) { - // Ignore horizontal scroll, only when zooming. - e.doit = false; + int mods = e.stateMask & SWT.MODIFIER_MASK; + // Treat horizontal touch pad/scroll wheel ourselves, rather than going through the + // scrollbar, since the scrollbar's size is limited. + e.doit = false; + if (mods == SWT.MOD1) { + // Ignore horizontal scroll, when zooming. + } else { + if (state.dragX( + state.getVisibleTime(), e.count * ((mods == SWT.SHIFT) ? TP_PAN_FAST : TP_PAN_SLOW))) { + canvas.redraw(Area.FULL, true); + } } }); canvas.addListener(SWT.Gesture, this::handleGesture); @@ -183,6 +199,9 @@ public TraceComposite(Composite parent, Analytics analytics, Theme theme) { case '0': redraw = state.setVisibleTime(state.getTraceTime()); break; + case 'i': + TraceMetadataDialog.showMetadata(getShell(), analytics, perfetto, theme); + break; } switch (e.character) { @@ -201,6 +220,11 @@ public TraceComposite(Composite parent, Analytics analytics, Theme theme) { protected abstract S createState(); protected abstract RootPanel createRootPanel(); + private void updateFilter(String search) { + rootPanel.updateFilter(search); + canvas.structureHasChanged(); + } + public S getState() { return state; } @@ -290,16 +314,32 @@ private static int permyriad(long v, long t) { private static class TopBar extends Composite { private final ToolBar toolBar; - public TopBar(Composite parent, Analytics analytics, Theme theme) { + // If onSearch is null, the search box and the info button are not shown. + public TopBar(Composite parent, Analytics analytics, Perfetto perfetto, Theme theme, Consumer onSearch) { super(parent, SWT.NONE); - setLayout(new GridLayout(3, false)); + setLayout(new GridLayout((onSearch == null) ? 3 : 5, false)); withLayoutData(createLabel(this, "Mode:"), new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); toolBar = withLayoutData(new ToolBar(this, SWT.FLAT | SWT.HORIZONTAL | SWT.TRAIL), - new GridData(SWT.FILL, SWT.CENTER, true, true)); + new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); + + if (onSearch != null) { + Text search = withLayoutData( + new Text(this, SWT.SINGLE | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL), + withSizeHints(new GridData(SWT.BEGINNING, SWT.CENTER, false, false), 300, SWT.DEFAULT)); + search.setMessage("Filter tracks by name..."); + withLayoutData(createButtonWithImage( + this, theme.info(), e -> showMetadata(getShell(), analytics, perfetto, theme)), + new GridData(SWT.END, SWT.CENTER, true, false)); + + search.addListener(SWT.Modify, e -> { + onSearch.accept(search.getText().trim().toLowerCase()); + }); + } + withLayoutData( createButtonWithImage(this, theme.help(), e -> showHelp(getShell(), analytics, theme)), - new GridData(SWT.END, SWT.CENTER, false, false)); + new GridData(SWT.END, SWT.CENTER, onSearch == null, false)); } public Consumer buildModeActions(Theme theme, Consumer onClick) { diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TraceConfigDialog.java b/gapic/src/main/com/google/gapid/perfetto/views/TraceConfigDialog.java index c91ff7795b..33554e0c54 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/TraceConfigDialog.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/TraceConfigDialog.java @@ -15,7 +15,6 @@ */ package com.google.gapid.perfetto.views; -import static com.google.gapid.proto.SettingsProto.Perfetto.Vulkan.CpuTiming.CPU_TIMING_COMMAND_BUFFER; import static com.google.gapid.proto.SettingsProto.Perfetto.Vulkan.CpuTiming.CPU_TIMING_DEVICE; import static com.google.gapid.proto.SettingsProto.Perfetto.Vulkan.CpuTiming.CPU_TIMING_INSTANCE; import static com.google.gapid.proto.SettingsProto.Perfetto.Vulkan.CpuTiming.CPU_TIMING_PHYSICAL_DEVICE; @@ -23,6 +22,7 @@ import static com.google.gapid.proto.SettingsProto.Perfetto.Vulkan.MemoryTracking.MEMORY_TRACKING_DEVICE; import static com.google.gapid.proto.SettingsProto.Perfetto.Vulkan.MemoryTracking.MEMORY_TRACKING_DRIVER; import static com.google.gapid.widgets.Widgets.createCheckbox; +import static com.google.gapid.widgets.Widgets.createCheckboxTableViewer; import static com.google.gapid.widgets.Widgets.createComposite; import static com.google.gapid.widgets.Widgets.createLabel; import static com.google.gapid.widgets.Widgets.createLink; @@ -32,15 +32,19 @@ import static com.google.gapid.widgets.Widgets.withIndents; import static com.google.gapid.widgets.Widgets.withLayoutData; import static com.google.gapid.widgets.Widgets.withMargin; +import static java.util.concurrent.TimeUnit.MICROSECONDS; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import static java.util.logging.Level.WARNING; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; +import static perfetto.protos.TraceConfigOuterClass.TraceConfig.BufferConfig.FillPolicy.RING_BUFFER; +import com.google.common.base.Predicate; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Sets; +import com.google.gapid.models.Devices; import com.google.gapid.models.Models; import com.google.gapid.models.Settings; import com.google.gapid.proto.SettingsProto; @@ -55,6 +59,12 @@ import com.google.protobuf.TextFormat.ParseException; import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.CheckStateChangedEvent; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ICheckStateListener; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; @@ -67,9 +77,6 @@ import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Spinner; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TableItem; import org.eclipse.swt.widgets.Text; import java.util.Arrays; @@ -80,58 +87,81 @@ import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; -import perfetto.protos.PerfettoConfig; -import perfetto.protos.PerfettoConfig.TraceConfig.BufferConfig.FillPolicy; +import perfetto.protos.AndroidPowerConfigOuterClass.AndroidPowerConfig; +import perfetto.protos.FtraceConfigOuterClass.FtraceConfig; +import perfetto.protos.GpuCounterConfigOuterClass.GpuCounterConfig; +import perfetto.protos.SysStatsCounters; +import perfetto.protos.TraceConfigOuterClass.TraceConfig; public class TraceConfigDialog extends DialogBase { protected static final Logger LOG = Logger.getLogger(TraceConfigDialog.class.getName()); - private static final int BUFFER_SIZE = 131072; + private static final int MAIN_BUFFER_SIZE = 131072; + private static final int PROC_BUFFER_SIZE = 4096; + private static final int PROC_BUFFER = 1; + // Kernel ftrace buffer size per CPU. private static final int FTRACE_BUFFER_SIZE = 8192; + + private static final int PROC_SCAN_PERIOD = 2000; + private static final int FTRACE_DRAIN_PERIOD = 250; + + private static final int MAX_IN_MEM_DURATION = 15 * 1000; + private static final int FLUSH_PERIOD = 5000; + private static final int WRITE_PERIOD = 2000; + private static final long MAX_FILE_SIZE = 2l * 1024 * 1024 * 1024; + + // These ftrace categories are always enabled to track process creation and ending. + private static final String[] PROCESS_TRACKING_FTRACE = { + "sched/sched_process_free", + "task/task_newtask", + "task/task_rename", + }; + // These ftrace categories are used to track CPU slices. private static final String[] CPU_BASE_FTRACE = { "sched/sched_switch", - "sched/sched_process_exit", - "sched/sched_process_free", - "task/task_newtask", - "task/task_rename", "power/suspend_resume", }; + // These ftrace categories provide CPU frequency data. private static final String[] CPU_FREQ_FTRACE = { "power/cpu_frequency", "power/cpu_idle" }; + // These ftrace categories provide scheduling dependency data. private static final String[] CPU_CHAIN_FTRACE = { "sched/sched_wakeup", "sched/sched_wakeup_new", "sched/sched_waking", }; + // These ftrace categories provide memory usage data. + private static final String[] MEM_FTRACE = { + "kmem/rss_stat", + }; private static final String[] CPU_SLICES_ATRACE = { - // TODO: this should come from the device. - "adb", "aidl", "am", "audio", "binder_driver", "binder_lock", "bionic", "camera", - "core_services", "dalvik", "database", "disk", "freq", "gfx", "hal", "idle", "input", - "ion", "memory", "memreclaim", "network", "nnapi", "pdx", "pm", "power", "res", "rro", "rs", - "sched", "sm", "ss", "sync", "vibrator", "video", "view", "webview", "wm", + "am", "audio", "gfx", "hal", "input", "pm", "power", "res", "rs", "sm", "video", "view", "wm", }; private static final String[] GPU_FREQ_FTRACE = { "power/gpu_frequency", }; - private static final PerfettoConfig.MeminfoCounters[] MEM_COUNTERS = { - PerfettoConfig.MeminfoCounters.MEMINFO_MEM_TOTAL, - PerfettoConfig.MeminfoCounters.MEMINFO_MEM_FREE, - PerfettoConfig.MeminfoCounters.MEMINFO_BUFFERS, - PerfettoConfig.MeminfoCounters.MEMINFO_CACHED, - PerfettoConfig.MeminfoCounters.MEMINFO_SWAP_CACHED, + private static final String[] GPU_MEM_FTRACE = { + "gpu_mem/gpu_mem_total", + }; + private static final SysStatsCounters.MeminfoCounters[] MEM_COUNTERS = { + SysStatsCounters.MeminfoCounters.MEMINFO_MEM_TOTAL, + SysStatsCounters.MeminfoCounters.MEMINFO_MEM_FREE, + SysStatsCounters.MeminfoCounters.MEMINFO_BUFFERS, + SysStatsCounters.MeminfoCounters.MEMINFO_CACHED, + SysStatsCounters.MeminfoCounters.MEMINFO_SWAP_CACHED, }; - private static final PerfettoConfig.AndroidPowerConfig.BatteryCounters[] BAT_COUNTERS = { - PerfettoConfig.AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT, - PerfettoConfig.AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE, - PerfettoConfig.AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT, + private static final AndroidPowerConfig.BatteryCounters[] BAT_COUNTERS = { + AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CAPACITY_PERCENT, + AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CHARGE, + AndroidPowerConfig.BatteryCounters.BATTERY_COUNTER_CURRENT, }; - private static final ImmutableMap VK_LABLES = + private static final ImmutableMap VK_LABELS = ImmutableMap. builder() - .put(CPU_TIMING_COMMAND_BUFFER, "VkCommandBuffer") .put(CPU_TIMING_DEVICE, "VkDevice") .put(CPU_TIMING_INSTANCE, "VkInstance") .put(CPU_TIMING_PHYSICAL_DEVICE, "VkPhysicalDevice") @@ -143,27 +173,28 @@ ImmutableMap. builder() private static final Pattern APP_REGEX = Pattern.compile("(?:[^:]*)?:([^/]+)(?:/[^/]+)"); private final Settings settings; - private final Device.PerfettoCapability caps; + private final Devices.DeviceCaptureInfo device; private InputArea input; public TraceConfigDialog( - Shell shell, Settings settings, Theme theme, Device.PerfettoCapability caps) { + Shell shell, Settings settings, Theme theme, Devices.DeviceCaptureInfo device) { super(shell, theme); this.settings = settings; - this.caps = caps; + this.device = device; } public static void showPerfettoConfigDialog( - Shell shell, Models models, Widgets widgets, Device.PerfettoCapability caps) { - new TraceConfigDialog(shell, models.settings, widgets.theme, caps).open(); + Shell shell, Models models, Widgets widgets, Devices.DeviceCaptureInfo device) { + new TraceConfigDialog(shell, models.settings, widgets.theme, device).open(); } - public static String getConfigSummary(Settings settings, Device.PerfettoCapability caps) { + public static String getConfigSummary(Settings settings, Devices.DeviceCaptureInfo device) { SettingsProto.PerfettoOrBuilder p = settings.perfetto(); if (p.getUseCustom()) { return "Custom"; } + Device.PerfettoCapability caps = device.device.getConfiguration().getPerfettoCapability(); List enabled = Lists.newArrayList(); if (p.getCpuOrBuilder().getEnabled()) { enabled.add("CPU"); @@ -171,7 +202,7 @@ public static String getConfigSummary(Settings settings, Device.PerfettoCapabili Device.GPUProfiling gpuCaps = caps.getGpuProfiling(); if (gpuCaps.getHasRenderStage() || gpuCaps.getGpuCounterDescriptor().getSpecsCount() > 0 || - gpuCaps.getHasFrameLifecycle()) { + caps.getHasFrameLifecycle()) { if (p.getGpuOrBuilder().getEnabled()) { enabled.add("GPU"); } @@ -193,31 +224,40 @@ public static String getConfigSummary(Settings settings, Device.PerfettoCapabili return enabled.stream().collect(joining(", ")); } - public static PerfettoConfig.TraceConfig.Builder getConfig( - Settings settings, Device.PerfettoCapability caps, String traceTarget) { + public static TraceConfig.Builder getConfig( + Settings settings, Devices.DeviceCaptureInfo device, String traceTarget, int duration) { SettingsProto.PerfettoOrBuilder p = settings.perfetto(); if (p.getUseCustom()) { - return p.getCustomConfig().toBuilder(); - } - - PerfettoConfig.TraceConfig.Builder config = PerfettoConfig.TraceConfig.newBuilder(); - PerfettoConfig.FtraceConfig.Builder ftrace = null; - if (p.getCpuOrBuilder().getEnabled() || (p.getGpuOrBuilder().getEnabled())) { - ftrace = config.addDataSourcesBuilder() - .getConfigBuilder() - .setName("linux.ftrace") - .getFtraceConfigBuilder() - .setBufferSizeKb(FTRACE_BUFFER_SIZE); + return p.getCustomConfig().toBuilder().setDurationMs(duration); } + Device.PerfettoCapability caps = device.device.getConfiguration().getPerfettoCapability(); + + TraceConfig.Builder config = TraceConfig.newBuilder(); + FtraceConfig.Builder ftrace = config.addDataSourcesBuilder() + .getConfigBuilder() + .setName("linux.ftrace") + .getFtraceConfigBuilder() + .addAllFtraceEvents(Arrays.asList(PROCESS_TRACKING_FTRACE)) + .setDrainPeriodMs(FTRACE_DRAIN_PERIOD) + .setBufferSizeKb(FTRACE_BUFFER_SIZE) + .setCompactSched(FtraceConfig.CompactSchedConfig.newBuilder() + .setEnabled(true)); + // Record process names at startup into the metadata buffer. + config.addDataSourcesBuilder() + .getConfigBuilder() + .setName("linux.process_stats") + .setTargetBuffer(PROC_BUFFER) + .getProcessStatsConfigBuilder() + .setScanAllProcessesOnStart(true); + // Periodically record process information into the main buffer. + config.addDataSourcesBuilder() + .getConfigBuilder() + .setName("linux.process_stats") + .getProcessStatsConfigBuilder() + .setProcStatsPollMs(PROC_SCAN_PERIOD) + .setProcStatsCacheTtlMs(10 * PROC_SCAN_PERIOD); if (p.getCpuOrBuilder().getEnabled()) { - // Record process names. - config.addDataSourcesBuilder() - .getConfigBuilder() - .setName("linux.process_stats") - .getProcessStatsConfigBuilder() - .setScanAllProcessesOnStart(true); - ftrace.addAllFtraceEvents(Arrays.asList(CPU_BASE_FTRACE)); if (p.getCpuOrBuilder().getFrequency()) { ftrace.addAllFtraceEvents(Arrays.asList(CPU_FREQ_FTRACE)); @@ -236,6 +276,7 @@ public static PerfettoConfig.TraceConfig.Builder getConfig( if (p.getGpuOrBuilder().getEnabled()) { ftrace.addAllFtraceEvents(Arrays.asList(GPU_FREQ_FTRACE)); + ftrace.addAllFtraceEvents(Arrays.asList(GPU_MEM_FTRACE)); Device.GPUProfiling gpuCaps = caps.getGpuProfiling(); SettingsProto.Perfetto.GPUOrBuilder gpu = p.getGpuOrBuilder(); @@ -243,28 +284,39 @@ public static PerfettoConfig.TraceConfig.Builder getConfig( config.addDataSourcesBuilder() .getConfigBuilder() .setName("gpu.renderstages"); - } - if (gpuCaps.getGpuCounterDescriptor().getSpecsCount() > 0 && - gpu.getCounters() && gpu.getCounterIdsCount() > 0) { - PerfettoConfig.GpuCounterConfig.Builder counters = config.addDataSourcesBuilder() - .getConfigBuilder() - .setName("gpu.counters") - .getGpuCounterConfigBuilder() - .setCounterPeriodNs(MILLISECONDS.toNanos(gpu.getCounterRate())); - counters.addAllCounterIds(gpu.getCounterIdsList()); - config.addDataSourcesBuilder() .getConfigBuilder() .setName("VulkanAPI"); } - if (gpuCaps.getHasFrameLifecycle() && gpu.getSurfaceFlinger()) { + if (gpuCaps.getGpuCounterDescriptor().getSpecsCount() > 0 && gpu.getCounters()) { + SettingsProto.Perfetto.GPUCounters enabledCounters = gpu.getCountersByGpuOrDefault( + device.device.getConfiguration().getHardware().getGPU().getName(), null); + if (enabledCounters != null && enabledCounters.getCounterIdsCount() > 0) { + GpuCounterConfig.Builder counters = config.addDataSourcesBuilder() + .getConfigBuilder() + .setName("gpu.counters") + .getGpuCounterConfigBuilder() + .setCounterPeriodNs(MICROSECONDS.toNanos(gpu.getCounterRate())); + counters.addAllCounterIds(enabledCounters.getCounterIdsList()); + } + } + if (caps.getHasFrameLifecycle() && gpu.getSurfaceFlinger()) { config.addDataSourcesBuilder() .getConfigBuilder() .setName("android.surfaceflinger.frame"); } + // Always enable GPU memory total counters, if available, because this + // datasource provides the initial counters upon tracing start, while the + // gpu_mem/gpu_mem_total ftrace tracepoint provides later counter updates. + if (gpuCaps.getHasGpuMemTotal()) { + config.addDataSourcesBuilder() + .getConfigBuilder() + .setName("android.gpu.memory"); + } } if (p.getMemoryOrBuilder().getEnabled()) { + ftrace.addAllFtraceEvents(Arrays.asList(MEM_FTRACE)); config.addDataSourcesBuilder() .getConfigBuilder() .setName("linux.sys_stats") @@ -279,16 +331,15 @@ public static PerfettoConfig.TraceConfig.Builder getConfig( .setName("android.power") .getAndroidPowerConfigBuilder() .setBatteryPollMs(p.getBatteryOrBuilder().getRate()) - .setCollectPowerRails(true) - .addAllBatteryCounters(Arrays.asList(BAT_COUNTERS)); + .addAllBatteryCounters(Arrays.asList(BAT_COUNTERS)) + .setCollectPowerRails(p.getBatteryOrBuilder().getCollectPowerRail()) + .setCollectEnergyEstimationBreakdown(p.getBatteryOrBuilder().getCollectEnergyBreakdown()); } - boolean largeBuffer = false; if (p.getVulkanOrBuilder().getEnabled()) { Device.VulkanProfilingLayers vkLayers = caps.getVulkanProfileLayers(); SettingsProto.Perfetto.VulkanOrBuilder vk = p.getVulkanOrBuilder(); if (vkLayers.getCpuTiming() && vk.getCpuTiming()) { - largeBuffer = true; config.addDataSourcesBuilder() .getConfigBuilder() .setName("VulkanCPUTiming") @@ -302,17 +353,31 @@ public static PerfettoConfig.TraceConfig.Builder getConfig( } } - config.addBuffers(PerfettoConfig.TraceConfig.BufferConfig.newBuilder() - .setSizeKb((largeBuffer ? 8 : 1) * BUFFER_SIZE) - .setFillPolicy(FillPolicy.DISCARD)); - config.setFlushPeriodMs((int)SECONDS.toMillis(5)); + // Buffer 0 (default): main buffer. + config.addBuffers(TraceConfig.BufferConfig.newBuilder() + .setSizeKb(MAIN_BUFFER_SIZE) + .setFillPolicy(RING_BUFFER)); + // Buffer 1: Initial process metadata. + config.addBuffers(TraceConfig.BufferConfig.newBuilder() + .setSizeKb(PROC_BUFFER_SIZE) + .setFillPolicy(RING_BUFFER)); + + config.setFlushPeriodMs(FLUSH_PERIOD); + config.setDurationMs(duration); + + if ((duration > MAX_IN_MEM_DURATION && !caps.getCanDownloadWhileTracing()) || + p.getForceTracingToFile()) { + config.setWriteIntoFile(true); + config.setFileWritePeriodMs(WRITE_PERIOD); + config.setMaxFileSizeBytes(MAX_FILE_SIZE); + } return config; } private static String vkLabels(List list) { return list.stream() - .map(VK_LABLES::get) + .map(VK_LABELS::get) .filter(Objects::nonNull) .distinct() .collect(joining(":")); @@ -331,7 +396,7 @@ protected Control createDialogArea(Composite parent) { InputArea[] areas = new InputArea[2]; areas[0] = new BasicInputArea( - container, settings, theme, caps, () -> switchTo(container, areas[1])); + container, settings, theme, device, () -> switchTo(container, areas[1])); areas[1] = new AdvancedInputArea( container, () -> switchTo(container, areas[0]), this::setOkButtonEnabled); @@ -365,6 +430,153 @@ protected void okPressed() { super.okPressed(); } + public static class GpuCountersDialog extends DialogBase { + private static final Predicate + SELECT_DEFAULT = GpuProfiling.GpuCounterDescriptor.GpuCounterSpec::getSelectByDefault; + + private final List specs; + private final Set currentIds; + + protected CheckboxTableViewer table; + private List selectedIds; + protected Set checkedElements; + private boolean hasFilters; + + public GpuCountersDialog( + Shell shell, Theme theme, List specs, + List currentIds) { + super(shell, theme); + this.specs = specs; + this.currentIds = Sets.newHashSet(currentIds); + this.checkedElements = Sets.newHashSet(); + this.hasFilters = false; + } + + public List getSpecs() { + return specs; + } + + public List getSelectedIds() { + return selectedIds; + } + + @Override + public String getTitle() { + return Messages.CAPTURE_TRACE_PERFETTO; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite)super.createDialogArea(parent); + createGpuCounterTable(area); + return area; + } + + protected void createGpuCounterTable(Composite area) { + Text search = new Text(area, SWT.SINGLE | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL); + search.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); + + table = createCheckboxTableViewer(area, SWT.NONE); + table.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Widgets.createTableColumn( + table, "Name", counter -> counter.getName()); + Widgets.createTableColumn( + table, "Description", counter -> counter.getDescription()); + table.setContentProvider(new ArrayContentProvider()); + table.setInput(specs); + table.setCheckedElements( + specs.stream() + .filter(c -> currentIds.contains(c.getCounterId())) + .toArray(GpuProfiling.GpuCounterDescriptor.GpuCounterSpec[]::new)); + table.getTable().getColumn(0).pack(); + table.getTable().getColumn(1).pack(); + collectCheckedElements(); + + table.addCheckStateListener(new ICheckStateListener() { + @Override + public void checkStateChanged(CheckStateChangedEvent event) { + if (event.getChecked()) { + checkedElements.add(event.getElement()); + } else { + checkedElements.remove(event.getElement()); + } + } + }); + + createLink(area, "Select none | default | all", e -> { + switch (e.text) { + case "none": + checkedElements.removeAll(Arrays.stream(table.getCheckedElements()).collect(Collectors.toSet())); + table.setAllChecked(false); + break; + case "default": + table.setCheckedElements( + specs.stream() + .filter(SELECT_DEFAULT) + .toArray(GpuProfiling.GpuCounterDescriptor.GpuCounterSpec[]::new)); + if (hasFilters) { + appendCheckedElements(); + } else { + collectCheckedElements(); + } + break; + case "all": + table.setAllChecked(true); + appendCheckedElements(); + break; + } + }); + + search.addListener(SWT.Modify, e -> { + String query = search.getText().trim().toLowerCase(); + if (query.isEmpty()) { + table.resetFilters(); + hasFilters = false; + resumeCheckedElements(); + return; + } + table.setFilters(new ViewerFilter() { + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + return ((GpuProfiling.GpuCounterDescriptor.GpuCounterSpec)element) + .getName() + .toLowerCase() + .contains(query); + } + }); + hasFilters = true; + resumeCheckedElements(); + }); + } + + @Override + protected Point getInitialSize() { + return new Point(convertHorizontalDLUsToPixels(450), convertVerticalDLUsToPixels(300)); + } + + @Override + protected void okPressed() { + selectedIds = Arrays.stream(checkedElements.toArray()) + .map(item -> (GpuProfiling.GpuCounterDescriptor.GpuCounterSpec)item) + .mapToInt(GpuProfiling.GpuCounterDescriptor.GpuCounterSpec::getCounterId) + .boxed() + .collect(toList()); + super.okPressed(); + } + + private void collectCheckedElements() { + checkedElements = Arrays.stream(table.getCheckedElements()).collect(Collectors.toSet()); + } + + private void appendCheckedElements() { + checkedElements.addAll(Arrays.stream(table.getCheckedElements()).collect(Collectors.toSet())); + } + + private void resumeCheckedElements() { + table.setCheckedElements(checkedElements.toArray()); + } + } + private static interface InputArea { public default void onSwitchedTo(@SuppressWarnings("unused") Settings settings) { // Do nothing. @@ -398,9 +610,11 @@ private static class BasicInputArea extends Composite implements InputArea { private final Button bat; private final Label[] batLabels; private final Spinner batRate; + private Button batPowerRail; + private Button batEnergyBreakDown; + private final Button vulkan; private final Button vulkanCPUTiming; - private final Button vulkanCPUTimingCommandBuffer; private final Button vulkanCPUTimingDevice; private final Button vulkanCPUTimingInstance; private final Button vulkanCPUTimingPhysicalDevice; @@ -410,8 +624,10 @@ private static class BasicInputArea extends Composite implements InputArea { private final Button vulkanMemoryTrackingDevice; private final Button vulkanMemoryTrackingDriver; + private final Button forceTraceToFile; + public BasicInputArea(Composite parent, Settings settings, Theme theme, - Device.PerfettoCapability caps, Runnable toAdvanced) { + Devices.DeviceCaptureInfo device, Runnable toAdvanced) { super(parent, SWT.NONE); setLayout(new GridLayout(1, false)); @@ -430,16 +646,17 @@ public BasicInputArea(Composite parent, Settings settings, Theme theme, cpuSlices = createCheckbox(cpuGroup, "Thread slices", sCpu.getSlices()); addSeparator(); + Device.PerfettoCapability caps = device.device.getConfiguration().getPerfettoCapability(); Device.GPUProfiling gpuCaps = caps.getGpuProfiling(); if (gpuCaps.getHasRenderStage() || gpuCaps.getGpuCounterDescriptor().getSpecsCount() > 0 || - gpuCaps.getHasFrameLifecycle()) { + caps.getHasFrameLifecycle()) { gpu = createCheckbox(this, "GPU", sGpu.getEnabled(), e -> updateGpu()); Composite gpuGroup = withLayoutData( createComposite(this, withMargin(new GridLayout(1, false), 5, 0)), withIndents(new GridData(), GROUP_INDENT, 0)); if (gpuCaps.getHasRenderStage()) { - gpuSlices = createCheckbox(gpuGroup, "Renderstage slices", sGpu.getSlices()); + gpuSlices = createCheckbox(gpuGroup, "GPU activity", sGpu.getSlices()); } else { gpuSlices = null; } @@ -452,20 +669,67 @@ public BasicInputArea(Composite parent, Settings settings, Theme theme, withIndents(new GridData(), GROUP_INDENT, 0)); gpuCountersLabels = new Label[3]; gpuCountersLabels[1] = createLabel(counterGroup, "Poll Rate:"); - gpuCountersRate = createSpinner(counterGroup, sGpu.getCounterRate(), 1, 1000); - gpuCountersLabels[2] = createLabel(counterGroup, "ms"); - gpuCountersLabels[0] = createLabel(counterGroup, sGpu.getCounterIdsCount() + " selected"); + long minSamplingPeriod = NANOSECONDS.toMicros(gpuCaps.getGpuCounterDescriptor().getMinSamplingPeriodNs()); + minSamplingPeriod = minSamplingPeriod > 0 ? minSamplingPeriod : MILLISECONDS.toMicros(1); + long maxSamplingPeriod = NANOSECONDS.toMicros(gpuCaps.getGpuCounterDescriptor().getMaxSamplingPeriodNs()); + maxSamplingPeriod = maxSamplingPeriod > 0 ? maxSamplingPeriod : minSamplingPeriod * 1000; + + long counterRate = Math.max(sGpu.getCounterRate(), minSamplingPeriod); + gpuCountersRate = createSpinner(counterGroup, (int)counterRate, + (int)minSamplingPeriod, (int)maxSamplingPeriod); + + // If the minimum sampling period is smaller than 1ms, it means GPU + // counters can be sampled at a higher rate than 1ms. And hence set + // the incremental steps to 100us. Otherwise, it means the sampling + // rate can not be faster than 1ms, and hence set the incremental + // steps to 1ms, which is 1000us. + if (MILLISECONDS.toMicros(1) > minSamplingPeriod) { + gpuCountersRate.setIncrement(100); + } else { + gpuCountersRate.setIncrement(1000); + } + gpuCountersLabels[2] = createLabel(counterGroup, "us"); + + String gpuName = device.device.getConfiguration().getHardware().getGPU().getName(); + List rememberedCounterIds = sGpu.getCountersByGpuOrDefault( + gpuName, SettingsProto.Perfetto.GPUCounters.getDefaultInstance()).getCounterIdsList(); + long count = caps.getGpuProfiling().getGpuCounterDescriptor().getSpecsList().stream() + .filter(c -> rememberedCounterIds.contains(c.getCounterId())).count(); + long total = caps.getGpuProfiling().getGpuCounterDescriptor().getSpecsCount(); + if (count == 0 && sGpu.getCounters()) { + // Select the default counters if none where remembered, but counters are turned on. + List ids = caps.getGpuProfiling().getGpuCounterDescriptor().getSpecsList() + .stream() + .filter(GpuProfiling.GpuCounterDescriptor.GpuCounterSpec::getSelectByDefault) + .mapToInt(GpuProfiling.GpuCounterDescriptor.GpuCounterSpec::getCounterId) + .boxed() + .collect(toList()); + settings.writePerfetto().getGpuBuilder().putCountersByGpu(gpuName, + SettingsProto.Perfetto.GPUCounters.newBuilder() + .addAllCounterIds(ids) + .build()); + count = ids.size(); + } + gpuCountersLabels[0] = createLabel(counterGroup, count + " of " + total + " selected"); gpuCountersSelect = Widgets.createButton(counterGroup, "Select", e -> { - GpuCountersDialog dialog = - new GpuCountersDialog(getShell(), theme, caps, sGpu.getCounterIdsList()); + List currentIds = settings.perfetto().getGpuOrBuilder().getCountersByGpuOrDefault( + gpuName, SettingsProto.Perfetto.GPUCounters.getDefaultInstance()).getCounterIdsList(); + GpuCountersDialog dialog = new GpuCountersDialog(getShell(), theme, + caps.getGpuProfiling().getGpuCounterDescriptor().getSpecsList(), currentIds); if (dialog.open() == Window.OK) { List newIds = dialog.getSelectedIds(); - settings.writePerfetto().getGpuBuilder() - .clearCounterIds() - .addAllCounterIds(newIds) - .setCounters(!newIds.isEmpty()); - gpuCountersLabels[0].setText(newIds.size() + " selected"); + if (newIds.isEmpty()) { + settings.writePerfetto().getGpuBuilder().setCounters(false); + } else { + settings.writePerfetto().getGpuBuilder() + .setCounters(true) + .putCountersByGpu(gpuName, SettingsProto.Perfetto.GPUCounters.newBuilder() + .addAllCounterIds(newIds) + .build()); + } + gpuCounters.setSelection(!newIds.isEmpty()); + gpuCountersLabels[0].setText(newIds.size() + " of " + total + " selected"); gpuCountersLabels[0].requestLayout(); updateGpu(); } @@ -477,7 +741,7 @@ public BasicInputArea(Composite parent, Settings settings, Theme theme, gpuCountersSelect = null; } - if (gpuCaps.getHasFrameLifecycle()) { + if (caps.getHasFrameLifecycle()) { gpuFrame = createCheckbox( gpuGroup, "Frame Lifecycle", sGpu.getSurfaceFlinger(), e -> updateGpu()); } else { @@ -505,13 +769,20 @@ public BasicInputArea(Composite parent, Settings settings, Theme theme, addSeparator(); bat = createCheckbox(this, "Battery", sBatt.getEnabled(), e -> updateBat()); - batLabels = new Label[2]; Composite batGroup = withLayoutData( - createComposite(this, withMargin(new GridLayout(3, false), 5, 0)), + createComposite(this, withMargin(new GridLayout(1, false), 5, 0)), + withIndents(new GridData(), GROUP_INDENT, 0)); + batLabels = new Label[2]; + Composite batLabelGroup = withLayoutData( + createComposite(batGroup, withMargin(new GridLayout(3, false), 5, 0)), withIndents(new GridData(), GROUP_INDENT, 0)); - batLabels[0] = createLabel(batGroup, "Poll Rate:"); - batRate = createSpinner(batGroup, sBatt.getRate(), 250, 60000); - batLabels[1] = createLabel(batGroup, "ms"); + batLabels[0] = createLabel(batLabelGroup, "Poll Rate:"); + batRate = createSpinner(batLabelGroup, sBatt.getRate(), 250, 60000); + batLabels[1] = createLabel(batLabelGroup, "ms"); + if (caps.getHasPowerRail()) { + batPowerRail = createCheckbox(batGroup, "Collect Power Rails", sBatt.getCollectPowerRail()); + batEnergyBreakDown = createCheckbox(batGroup, "Collect Energy Breakdown", sBatt.getCollectEnergyBreakdown()); + } Device.VulkanProfilingLayers vkLayers = caps.getVulkanProfileLayers(); if (vkLayers.getCpuTiming() || vkLayers.getMemoryTracker()) { @@ -536,15 +807,12 @@ public BasicInputArea(Composite parent, Settings settings, Theme theme, createCheckbox(cpuTimingGroup, "Device", hasCategory(sVk, CPU_TIMING_DEVICE)); vulkanCPUTimingQueue = createCheckbox(cpuTimingGroup, "Queue", hasCategory(sVk, CPU_TIMING_QUEUE)); - vulkanCPUTimingCommandBuffer = createCheckbox( - cpuTimingGroup, "CommandBuffer", hasCategory(sVk, CPU_TIMING_COMMAND_BUFFER)); } else { vulkanCPUTiming = null; vulkanCPUTimingInstance = null; vulkanCPUTimingPhysicalDevice = null; vulkanCPUTimingDevice = null; vulkanCPUTimingQueue = null; - vulkanCPUTimingCommandBuffer = null; } if (caps.getVulkanProfileLayers().getMemoryTracker()) { vulkanMemoryTracking = createCheckbox( @@ -569,16 +837,28 @@ public BasicInputArea(Composite parent, Settings settings, Theme theme, vulkanCPUTimingPhysicalDevice = null; vulkanCPUTimingDevice = null; vulkanCPUTimingQueue = null; - vulkanCPUTimingCommandBuffer = null; vulkanMemoryTracking = null; vulkanMemoryTrackingDevice = null; vulkanMemoryTrackingDriver = null; } + if (caps.getCanDownloadWhileTracing()) { + addSeparator(); + forceTraceToFile = createCheckbox(this, "Force tracing to a file on the device", + settings.perfetto().getForceTracingToFile()); + forceTraceToFile.setToolTipText("Only use this if the USB throughput is too low, causing " + + "buffer stalls and loss of tracing data."); + } else { + forceTraceToFile = null; + } + withLayoutData(createLink(this, "Switch to advanced mode", e -> { // Remember the input thus far and turn it into a proto to be modified by the user. update(settings); - settings.writePerfetto().setCustomConfig(getConfig(settings, caps, "")); + settings.writePerfetto().setCustomConfig( + // Use a config that writes to file for custom by default. + getConfig(settings, device, "", MAX_IN_MEM_DURATION + 1) + .clearDurationMs()); toAdvanced.run(); }), new GridData(SWT.END, SWT.BEGINNING, false, false)); @@ -631,6 +911,13 @@ public void update(Settings settings) { sMem.setRate(memRate.getSelection()); sBatt.setEnabled(bat.getSelection()); sBatt.setRate(batRate.getSelection()); + if (batPowerRail != null) { + sBatt.setCollectPowerRail(batPowerRail.getSelection()); + } + + if (batEnergyBreakDown != null) { + sBatt.setCollectEnergyBreakdown(batEnergyBreakDown.getSelection()); + } if (vulkan != null) { sVk.setEnabled(vulkan.getSelection()); @@ -638,7 +925,6 @@ public void update(Settings settings) { if (vulkanCPUTiming != null) { sVk.setCpuTiming(vulkanCPUTiming.getSelection()); sVk.clearCpuTimingCategories(); - addCategory(vulkanCPUTimingCommandBuffer, sVk, CPU_TIMING_COMMAND_BUFFER); addCategory(vulkanCPUTimingDevice, sVk, CPU_TIMING_DEVICE); addCategory(vulkanCPUTimingPhysicalDevice, sVk, CPU_TIMING_PHYSICAL_DEVICE); addCategory(vulkanCPUTimingInstance, sVk, CPU_TIMING_INSTANCE); @@ -650,6 +936,9 @@ public void update(Settings settings) { addCategory(vulkanMemoryTrackingDevice, sVk, MEMORY_TRACKING_DEVICE); addCategory(vulkanMemoryTrackingDriver, sVk, MEMORY_TRACKING_DRIVER); } + + settings.writePerfetto().setForceTracingToFile( + forceTraceToFile != null && forceTraceToFile.getSelection()); } private static void addCategory(Button checkbox, SettingsProto.Perfetto.Vulkan.Builder vk, @@ -691,7 +980,7 @@ private void updateGpu() { gpuCounters.setEnabled(enabled); boolean countersEnabled = enabled && gpuCounters.getSelection(); gpuCountersRate.setEnabled(countersEnabled); - gpuCountersSelect.setEnabled(countersEnabled); + gpuCountersSelect.setEnabled(enabled); for (Label label : gpuCountersLabels) { label.setEnabled(countersEnabled); } @@ -714,7 +1003,6 @@ private void updateVulkan() { vulkanCPUTimingPhysicalDevice.setEnabled(enabled); vulkanCPUTimingDevice.setEnabled(enabled); vulkanCPUTimingQueue.setEnabled(enabled); - vulkanCPUTimingCommandBuffer.setEnabled(enabled); } if (vulkanMemoryTracking != null) { vulkanMemoryTracking.setEnabled(vkEnabled); @@ -738,73 +1026,11 @@ private void updateBat() { for (Label label : batLabels) { label.setEnabled(enabled); } - } - - private static class GpuCountersDialog extends DialogBase { - private final Device.PerfettoCapability caps; - private final Set currentIds; - - private Table table; - private List selectedIds; - - public GpuCountersDialog( - Shell shell, Theme theme, Device.PerfettoCapability caps, List currentIds) { - super(shell, theme); - this.caps = caps; - this.currentIds = Sets.newHashSet(currentIds); - } - - public List getSelectedIds() { - return selectedIds; + if (batPowerRail != null) { + batPowerRail.setEnabled(enabled); } - - @Override - public String getTitle() { - return Messages.CAPTURE_TRACE_PERFETTO; - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite area = (Composite)super.createDialogArea(parent); - table = withLayoutData(new Table(area, SWT.CHECK), new GridData(GridData.FILL_BOTH)); - table.setHeaderVisible(true); - table.setLinesVisible(true); - new TableColumn(table, SWT.NONE).setText("Name"); - new TableColumn(table, SWT.NONE).setText("Description"); - for (GpuProfiling.GpuCounterDescriptor.GpuCounterSpec counter : - caps.getGpuProfiling().getGpuCounterDescriptor().getSpecsList()) { - TableItem item = new TableItem(table, SWT.NONE); - item.setText(new String[] { counter.getName(), counter.getDescription() }); - item.setData(counter); - if (currentIds.contains(counter.getCounterId())) { - item.setChecked(true); - } - } - table.getColumn(0).pack(); - table.getColumn(1).pack(); - createLink(area, "Select none | all", e -> { - boolean checked = "all".equals(e.text); - for (TableItem item : table.getItems()) { - item.setChecked(checked); - } - }); - return area; - } - - @Override - protected Point getInitialSize() { - return new Point(convertHorizontalDLUsToPixels(450), convertVerticalDLUsToPixels(300)); - } - - @Override - protected void okPressed() { - selectedIds = Arrays.stream(table.getItems()) - .filter(item -> item.getChecked()) - .map(item -> (GpuProfiling.GpuCounterDescriptor.GpuCounterSpec)item.getData()) - .mapToInt(GpuProfiling.GpuCounterDescriptor.GpuCounterSpec::getCounterId) - .boxed() - .collect(toList()); - super.okPressed(); + if (batEnergyBreakDown != null) { + batEnergyBreakDown.setEnabled(enabled); } } } @@ -826,7 +1052,7 @@ public AdvancedInputArea(Composite parent, Runnable toBasic, Consumer o input.addListener(SWT.Modify, ev -> { try { - TextFormat.merge(input.getText(), PerfettoConfig.TraceConfig.newBuilder()); + TextFormat.merge(input.getText(), TraceConfig.newBuilder()); error.setVisible(false); error.setText(""); okEnabled.accept(true); @@ -841,7 +1067,7 @@ public AdvancedInputArea(Composite parent, Runnable toBasic, Consumer o @Override public void onSwitchedTo(Settings settings) { - input.setText(TextFormat.printToString(settings.perfetto().getCustomConfig())); + input.setText(TextFormat.printer().printToString(settings.perfetto().getCustomConfig())); } @Override diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TraceMetadataDialog.java b/gapic/src/main/com/google/gapid/perfetto/views/TraceMetadataDialog.java new file mode 100644 index 0000000000..f1b847604e --- /dev/null +++ b/gapic/src/main/com/google/gapid/perfetto/views/TraceMetadataDialog.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ + +package com.google.gapid.perfetto.views; + +import static com.google.gapid.widgets.Widgets.createTableViewer; +import static com.google.gapid.widgets.Widgets.packColumns; + +import com.google.gapid.models.Analytics; +import com.google.gapid.models.Perfetto; +import com.google.gapid.perfetto.QueryViewer.ResultContentProvider; +import com.google.gapid.perfetto.QueryViewer.Row; +import com.google.gapid.proto.perfetto.Perfetto.QueryResult; +import com.google.gapid.proto.service.Service.ClientAction; +import com.google.gapid.rpc.Rpc; +import com.google.gapid.rpc.Rpc.Result; +import com.google.gapid.rpc.RpcException; +import com.google.gapid.rpc.UiCallback; +import com.google.gapid.util.Messages; +import com.google.gapid.widgets.DialogBase; +import com.google.gapid.widgets.Theme; +import com.google.gapid.widgets.Widgets; +import java.util.concurrent.ExecutionException; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.TableColumn; + +public class TraceMetadataDialog { + + private static final Logger LOG = Logger.getLogger(TraceMetadataDialog.class.getName()); + private static final String QUERY_STR = + "SELECT name AS 'Name', CAST(COALESCE(int_value, str_value, 'NULL') as TEXT) as 'Value' FROM" + + " metadata;"; + + public static void showMetadata( + Shell shell, Analytics analytics, Perfetto perfetto, Theme theme) { + analytics.postInteraction(Analytics.View.QueryMetadata, ClientAction.Show); + new DialogBase(shell, theme) { + @Override + public String getTitle() { + return Messages.TRACE_METADATA_VIEW_TITLE; + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite area = (Composite) super.createDialogArea(parent); + TableViewer table = createTableViewer(area, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); + table.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + table.setContentProvider(new ResultContentProvider()); + table.setLabelProvider(new LabelProvider()); + Rpc.listen( + perfetto.query(QUERY_STR), + new UiCallback(area, LOG) { + @Override + protected QueryResult onRpcThread(Result result) + throws ExecutionException { + try { + return result.get(); + } catch (RpcException e) { + LOG.log(Level.WARNING, "System Profile Query failure", e); + return QueryResult.newBuilder().setError(e.toString()).build(); + } + } + + @Override + protected void onUiThread(QueryResult result) { + table.setInput(null); + for (TableColumn col : table.getTable().getColumns()) { + col.dispose(); + } + + if (!result.getError().isEmpty()) { + Widgets.createLabel(area, "Error: " + result.getError()); + area.requestLayout(); + } else if (result.getNumRecords() == 0) { + Widgets.createLabel(area, "Query returned no rows."); + area.requestLayout(); + } else { + for (int i = 0; i < result.getColumnDescriptorsCount(); i++) { + int col = i; + QueryResult.ColumnDesc desc = result.getColumnDescriptors(i); + Widgets.createTableColumn( + table, desc.getName(), row -> ((Row) row).getValue(col)); + table.setInput(result); + packColumns(table.getTable()); + table.getTable().requestLayout(); + } + } + } + }); + return area; + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + } + }.open(); + } +} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java b/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java index d4f994530f..dedd4fc7a7 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/TrackContainer.java @@ -53,19 +53,19 @@ private TrackContainer() { } public static > TrackConfig.Track.UiFactory single( - TrackConfig.Track.UiFactory track, boolean sep) { - return state -> new Single(state, track.createPanel(state), sep, null, true); + TrackConfig.Track.UiFactory track, boolean sep, boolean rightTruncate) { + return state -> new Single(state, track.createPanel(state), sep, null, true, rightTruncate); } public static > TrackConfig.Track.UiFactory single( - TrackConfig.Track.UiFactory track, boolean sep, BiConsumer filter, - boolean initial) { + TrackConfig.Track.UiFactory track, boolean sep, BiConsumer toggleDetails, + boolean showDetails, boolean rightTruncate) { return state -> { T panel = track.createPanel(state); - if (initial) { - filter.accept(panel, initial); + if (!showDetails) { + toggleDetails.accept(panel, false); } - return new Single(state, panel, sep, filter, initial); + return new Single(state, panel, sep, toggleDetails, showDetails, rightTruncate); }; } @@ -82,50 +82,58 @@ public static > TrackConfig.Group.UiFac public static > TrackConfig.Group.UiFactory group( TrackConfig.Track.UiFactory summary, boolean expanded, - BiConsumer filter, boolean initial) { + BiConsumer toggleDetails, boolean showDetails) { return (state, detail) -> { CopyablePanel.Group group = new CopyablePanel.Group(); for (CopyablePanel track : detail) { group.add(track); } - if (initial) { - filter.accept(group, true); + if (!showDetails) { + toggleDetails.accept(group, false); } - return Group.of(state, summary.createPanel(state), group, expanded, filter, initial); + return Group.of( + state, summary.createPanel(state), group, expanded, toggleDetails, showDetails); }; } private static class Single> extends Panel.Base - implements CopyablePanel> { + implements CopyablePanel>, FilterablePanel { private final T track; private final boolean sep; - protected final BiConsumer filter; + protected final BiConsumer toggleDetails; private final PinState pinState; + private final boolean rightTruncate; // False -> Left truncate, True -> Right truncate - protected boolean filtered; + protected boolean showDetails; protected boolean hovered = false; - public Single(State.ForSystemTrace state, T track, boolean sep, BiConsumer filter, - boolean filtered) { - this(track ,sep, filter, filtered, new PinState(state)); + public Single(State.ForSystemTrace state, T track, boolean sep, + BiConsumer toggleDetails, boolean showDetails, boolean rightTruncate) { + this(track, sep, toggleDetails, showDetails, new PinState(state), rightTruncate); } - private Single(T track, boolean sep, BiConsumer filter, - boolean filtered, PinState pinState) { + private Single(T track, boolean sep, BiConsumer toggleDetails, boolean showDetails, + PinState pinState, boolean rightTruncate) { this.track = track; this.sep = sep; - this.filter = filter; + this.toggleDetails = toggleDetails; this.pinState = pinState; - this.filtered = filtered; + this.showDetails = showDetails; + this.rightTruncate = rightTruncate; } @Override public Single copy() { - return new Single(track.copy(), sep, filter, filtered, pinState); + return new Single(track.copy(), sep, toggleDetails, showDetails, pinState, rightTruncate); } private Single copyWithSeparator() { - return new Single(track.copy(), true, filter, filtered, pinState); + return new Single(track.copy(), true, toggleDetails, showDetails, pinState, rightTruncate); + } + + @Override + public boolean include(String search) { + return track.getTitle().toLowerCase().contains(search); } @Override @@ -143,11 +151,11 @@ public void setSize(double w, double h) { public void render(RenderContext ctx, Repainter repainter) { ctx.withClip(0, 0, LABEL_WIDTH, height, () -> { ctx.setForegroundColor(colors().textMain); - ctx.drawTextLeftTruncate(Fonts.Style.Normal, track.getTitle(), LABEL_OFFSET, 0, - ((filter == null) ? LABEL_PIN_X : LABEL_TOGGLE_X) - LABEL_MARGIN - LABEL_OFFSET, - TITLE_HEIGHT); - if (filter != null) { - ctx.drawIcon(filtered ? unfoldMore(ctx.theme) : unfoldLess(ctx.theme), + ctx.drawTextTruncate(Fonts.Style.Normal, track.getTitle(), LABEL_OFFSET, 0, + ((toggleDetails == null) ? LABEL_PIN_X : LABEL_TOGGLE_X) - LABEL_MARGIN - LABEL_OFFSET, + TITLE_HEIGHT, rightTruncate); + if (toggleDetails != null) { + ctx.drawIcon(showDetails ? unfoldLess(ctx.theme) : unfoldMore(ctx.theme), LABEL_TOGGLE_X, 0, TITLE_HEIGHT); } if (hovered || pinState.isPinned()) { @@ -173,22 +181,23 @@ public Dragger onDragStart(double x, double y, int mods) { } @Override - public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { + public Hover onMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { if (x < LABEL_WIDTH) { hovered = true; - if (filter != null && y < TITLE_HEIGHT && x >= LABEL_TOGGLE_X && x < LABEL_PIN_X) { - return new TrackTitleHover(track.onMouseMove(m, x, y, mods), () -> { - filtered = !filtered; - filter.accept(track, filtered); + if (toggleDetails != null && y < TITLE_HEIGHT && x >= LABEL_TOGGLE_X && x < LABEL_PIN_X) { + return new TrackTitleHover(track.onMouseMove(m, repainter, x, y, mods), () -> { + showDetails = !showDetails; + toggleDetails.accept(track, showDetails); }); } else if (y < TITLE_HEIGHT && x >= LABEL_PIN_X) { - return new TrackTitleHover( - track.onMouseMove(m, x, y, mods), () -> pinState.toggle(this::copyWithSeparator)); + return new TrackTitleHover(track.onMouseMove(m, repainter, x, y, mods), + () -> pinState.toggle(this::copyWithSeparator)); } else { - return new TrackTitleHover(track.onMouseMove(m, x, y, mods), null); + return new TrackTitleHover(track.onMouseMove(m, repainter, x, y, mods), null); } } else { - return track.onMouseMove(m, x, y, mods); + return track.onMouseMove(m, repainter, x, y, mods); } } @@ -206,50 +215,56 @@ public void stop() { } private static class Group & TitledPanel, D extends CopyablePanel> - extends Panel.Base implements CopyablePanel> { + extends Panel.Base implements CopyablePanel>, Panel.Grouper, FilterablePanel { private final T summary; - private final CopyablePanel.Group detail; - protected final BiConsumer filter; + private final CopyablePanel.Group children; + protected final BiConsumer toggleDetails; private final PinState pinState; protected boolean expanded; - protected boolean filtered; + protected boolean showDetails; protected boolean hovered = false; - private Group(T summary, CopyablePanel.Group detail, boolean expanded, - BiConsumer filter, boolean filtered, PinState pinState) { + private Group(T summary, CopyablePanel.Group children, boolean expanded, + BiConsumer toggleDetails, boolean showDetails, + PinState pinState) { this.summary = summary; - this.detail = detail; + this.children = children; this.expanded = expanded; - this.filter = filter; - this.filtered = filtered; + this.toggleDetails = toggleDetails; + this.showDetails = showDetails; this.pinState = pinState; } public static & TitledPanel, D extends CopyablePanel> TrackContainer.Group of(State.ForSystemTrace state, T summary, CopyablePanel.Group detail, boolean expanded, - BiConsumer filter, boolean filtered) { + BiConsumer toggleDetails, boolean showDetails) { return new TrackContainer.Group( - summary, detail, expanded, filter, filtered, new PinState(state)); + summary, detail, expanded, toggleDetails, showDetails, new PinState(state)); } @Override public TrackContainer.Group copy() { return new TrackContainer.Group( - summary.copy(), detail.copy(), expanded, filter, filtered, pinState); + summary.copy(), children.copy(), expanded, toggleDetails, showDetails, pinState); + } + + @Override + public boolean include(String search) { + return summary.getTitle().toLowerCase().contains(search); } @Override public double getPreferredHeight() { - return expanded ? TITLE_HEIGHT + detail.getPreferredHeight() : summary.getPreferredHeight(); + return expanded ? TITLE_HEIGHT + children.getPreferredHeight() : summary.getPreferredHeight(); } @Override public void setSize(double w, double h) { super.setSize(w, h); if (expanded) { - detail.setSize(w, h - TITLE_HEIGHT); + children.setSize(w, h - TITLE_HEIGHT); } else { summary.setSize(w, h); } @@ -267,9 +282,9 @@ public void render(RenderContext ctx, Repainter repainter) { double x = Math.max(LABEL_TOGGLE_X, LABEL_OFFSET + Math.ceil(ctx.measure(Fonts.Style.Normal, summary.getTitle()).w) + LABEL_MARGIN); - if (filter != null) { + if (toggleDetails != null) { ctx.drawIcon( - filtered ? unfoldMore(ctx.theme) : unfoldLess(ctx.theme), x, 0, TITLE_HEIGHT); + showDetails ? unfoldLess(ctx.theme) : unfoldMore(ctx.theme), x, 0, TITLE_HEIGHT); x += LABEL_ICON_SIZE; } if (hovered || pinState.isPinned()) { @@ -281,7 +296,7 @@ public void render(RenderContext ctx, Repainter repainter) { summary.decorateTitle(ctx, repainter); ctx.withTranslation(0, TITLE_HEIGHT, () -> - detail.render(ctx, repainter.translated(0, TITLE_HEIGHT))); + children.render(ctx, repainter.translated(0, TITLE_HEIGHT))); } else { ctx.withClip(0, 0, LABEL_WIDTH, height, () -> { ctx.setForegroundColor(colors().textMain); @@ -309,7 +324,7 @@ public void visit(Visitor v, Area area) { super.visit(v, area); if (expanded) { area.intersect(0, TITLE_HEIGHT, width, height - TITLE_HEIGHT) - .ifNotEmpty(a -> detail.visit(v, area.translate(0, -TITLE_HEIGHT))); + .ifNotEmpty(a -> children.visit(v, area.translate(0, -TITLE_HEIGHT))); } else { summary.visit(v, area); } @@ -319,20 +334,21 @@ public void visit(Visitor v, Area area) { public Dragger onDragStart(double x, double y, int mods) { if (expanded) { return (y < TITLE_HEIGHT) ? Dragger.NONE : - detail.onDragStart(x, y - TITLE_HEIGHT, mods).translated(0, TITLE_HEIGHT); + children.onDragStart(x, y - TITLE_HEIGHT, mods).translated(0, TITLE_HEIGHT); } else { return summary.onDragStart(x, y, mods); } } @Override - public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { + public Hover onMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { if (y < TITLE_HEIGHT && (expanded || x < LABEL_WIDTH)) { hovered = true; double textEnd = Math.ceil(m.measure(Fonts.Style.Normal, summary.getTitle()).w) + LABEL_OFFSET; double gapEnd = expanded ? Math.max(textEnd, LABEL_TOGGLE_X) : LABEL_TOGGLE_X; - double toggleEnd = (expanded && filter != null) ? gapEnd + LABEL_ICON_SIZE : gapEnd; + double toggleEnd = (expanded && toggleDetails != null) ? gapEnd + LABEL_ICON_SIZE : gapEnd; double pinEnd = Math.max(LABEL_PIN_X, toggleEnd) + LABEL_ICON_SIZE; double redraw = (pinEnd > LABEL_WIDTH) ? pinEnd + LABEL_MARGIN : 0; if (expanded) { @@ -341,15 +357,16 @@ public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { } } else { if (x < Math.min(textEnd, LABEL_PIN_X - LABEL_MARGIN)) { - return new TrackTitleHover(summary.onMouseMove(m, x, y, mods), redraw, () -> expanded = true); + return new TrackTitleHover( + summary.onMouseMove(m, repainter, x, y, mods), redraw, () -> expanded = true); } toggleEnd = LABEL_PIN_X; pinEnd = LABEL_WIDTH; } - if (expanded && filter != null && x >= gapEnd && x < toggleEnd) { + if (expanded && toggleDetails != null && x >= gapEnd && x < toggleEnd) { return new TrackTitleHover(Hover.NONE, redraw, () -> { - filtered = !filtered; - filter.accept(detail, filtered); + showDetails = !showDetails; + toggleDetails.accept(children, showDetails); }); } if (x >= toggleEnd && x < pinEnd) { @@ -362,12 +379,34 @@ public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { } if (expanded) { - return detail.onMouseMove(m, x, y - TITLE_HEIGHT, mods).translated(0, TITLE_HEIGHT); + return children.onMouseMove( + m, repainter.translated(0, TITLE_HEIGHT), x, y - TITLE_HEIGHT, mods + ).translated(0, TITLE_HEIGHT); } else { - return summary.onMouseMove(m, x, y, mods); + return summary.onMouseMove(m, repainter, x, y, mods); } } + @Override + public int getPanelCount() { + return children.getPanelCount(); + } + + @Override + public Panel getPanel(int idx) { + return children.getPanel(idx); + } + + @Override + public void setVisible(int idx, boolean visible) { + children.setVisible(idx, visible); + } + + @Override + public void setFiltered(int idx, boolean filtered) { + children.setFiltered(idx, filtered); + } + private class TrackTitleHover extends TrackContainer.TrackTitleHover { private double redraw; diff --git a/gapic/src/main/com/google/gapid/perfetto/views/TrackPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/TrackPanel.java index 395de6a32c..c2aa0b7c69 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/TrackPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/TrackPanel.java @@ -15,24 +15,19 @@ */ package com.google.gapid.perfetto.views; -import static com.google.common.base.CharMatcher.whitespace; +import static com.google.gapid.perfetto.canvas.Tooltip.LocationComputer.fixedLocation; import static com.google.gapid.perfetto.views.StyleConstants.LABEL_WIDTH; import static com.google.gapid.perfetto.views.StyleConstants.TRACK_MARGIN; -import static com.google.gapid.perfetto.views.StyleConstants.colors; import static com.google.gapid.perfetto.views.TimelinePanel.drawGridLines; -import com.google.common.base.CharMatcher; -import com.google.common.base.Splitter; -import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; import com.google.gapid.perfetto.canvas.Panel; import com.google.gapid.perfetto.canvas.RenderContext; -import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.canvas.Tooltip; import com.google.gapid.perfetto.models.Track; -import java.util.List; import java.util.function.Consumer; /** @@ -42,7 +37,6 @@ public abstract class TrackPanel> extends Panel.Base implements TitledPanel, CopyablePanel { private static final double HOVER_X_OFF = 10; private static final double HOVER_Y_OFF = 7; - private static final double HOVER_PADDING = 4; protected final State state; protected Tooltip tooltip; @@ -68,18 +62,7 @@ public void render(RenderContext ctx, Repainter repainter) { if (tooltip != null) { ctx.addOverlay(() -> { - ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect(tooltip.x, tooltip.y, - tooltip.width + 2 * HOVER_PADDING, tooltip.height + 2 * HOVER_PADDING); - ctx.setForegroundColor(colors().panelBorder); - ctx.drawRect(tooltip.x, tooltip.y, - tooltip.width + 2 * HOVER_PADDING - 1, tooltip.height + 2 * HOVER_PADDING - 1); - ctx.setForegroundColor(colors().textMain); - - double tx = tooltip.x + HOVER_PADDING, ty = tooltip.y + HOVER_PADDING; - for (Tooltip.Line line : tooltip.lines) { - line.render(ctx, tx, ty); - } + tooltip.render(ctx); }); } } @@ -93,19 +76,19 @@ public void visit(Visitor v, Area area) { } @Override - public Hover onMouseMove(Fonts.TextMeasurer m, double x, double y, int mods) { + public Hover onMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { if (x < LABEL_WIDTH) { String text = getTooltip(); if (text.isEmpty()) { return Hover.NONE; } - tooltip = Tooltip.compute(m, text, x + HOVER_X_OFF, y + HOVER_Y_OFF); + tooltip = Tooltip.forText(m, text, fixedLocation(x + HOVER_X_OFF, y + HOVER_Y_OFF)); return new Hover() { @Override public Area getRedraw() { - return new Area(tooltip.x, tooltip.y, - 2 * HOVER_PADDING + tooltip.width, 2 * HOVER_PADDING + tooltip.height); + return tooltip.getArea(); } @Override @@ -121,16 +104,13 @@ public void stop() { } else if (y < TRACK_MARGIN || y > height - TRACK_MARGIN) { return Hover.NONE; } - return onTrackMouseMove(m, x - LABEL_WIDTH, y - TRACK_MARGIN, mods) - .translated(LABEL_WIDTH, TRACK_MARGIN); + return onTrackMouseMove(m, repainter.translated(LABEL_WIDTH, TRACK_MARGIN), + x - LABEL_WIDTH, y - TRACK_MARGIN, mods + ).translated(LABEL_WIDTH, TRACK_MARGIN); } - protected abstract Hover onTrackMouseMove(Fonts.TextMeasurer m, double x, double y, int mods); - - // Helper functions for the track.getData(..) calls. - protected Track.OnUiThread onUiThread() { - return onUiThread(state, () -> { /* do nothing */ }); - } + protected abstract Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods); // Helper functions for the track.getData(..) calls. protected Track.OnUiThread onUiThread(Repainter repainter) { @@ -162,136 +142,4 @@ protected StyleConstants.Gradient getSliceColor(String title, int depth) { protected StyleConstants.Gradient getSliceColor(String title) { return getSliceColor(title, 0); } - - private static class Tooltip { - private static final Splitter LINE_SPLITTER = - Splitter.on(CharMatcher.anyOf("\r\n")).omitEmptyStrings().trimResults(); - private static final int MAX_WIDTH = 400; - - public final double x, y; - public final Line[] lines; - public final double width; - public final double height; - - public Tooltip(double x, double y, Line[] lines, double width, double height) { - this.x = x; - this.y = y; - this.lines = lines; - this.width = width; - this.height = height; - } - - public static Tooltip compute(Fonts.TextMeasurer m, String text, double x, double y) { - Builder builder = new Builder(m.measure(Fonts.Style.Normal, " ")); - para: for (String paragraph : LINE_SPLITTER.split(text)) { - boolean first = true; - Fonts.Style style = Fonts.Style.Normal; - if (paragraph.startsWith("\\b")) { - style = Fonts.Style.Bold; - paragraph = paragraph.substring(2); - } - - do { - Size size = m.measure(style, paragraph); - if (size.w <= MAX_WIDTH) { - builder.addLine(paragraph, style, size, first); - continue para; - } - - int guess = (int)(MAX_WIDTH * paragraph.length() / size.w); - while (guess < paragraph.length() && !whitespace().matches(paragraph.charAt(guess))) { - guess++; - } - size = m.measure(style, paragraph.substring(0, guess)); - - if (size.w <= MAX_WIDTH) { - do { - int next = guess + 1; - while (next < paragraph.length() && !whitespace().matches(paragraph.charAt(next))) { - next++; - } - Size now = m.measure(style, paragraph.substring(0, next)); - if (now.w <= MAX_WIDTH) { - guess = next; - size = now; - } else { - break; - } - } while (guess < paragraph.length()); - builder.addLine(paragraph.substring(0, guess), style, size, first); - paragraph = paragraph.substring(guess).trim(); - first = false; - } else { - do { - int next = guess - 1; - while (next > 0 && !whitespace().matches(paragraph.charAt(next))) { - next--; - } - - if (next == 0) { - // We have a single word longer than our max width. Blow our limit. - builder.addLine(paragraph.substring(0, guess), style, size, first); - paragraph = paragraph.substring(guess).trim(); - first = false; - break; - } - - guess = next; - size = m.measure(style, paragraph.substring(0, next)); - if (size.w <= MAX_WIDTH) { - builder.addLine(paragraph.substring(0, guess), style, size, first); - paragraph = paragraph.substring(guess).trim(); - first = false; - break; - } - } while (true); - } - } while (!paragraph.isEmpty()); - } - return builder.build(x, y); - } - - public static class Line { - private final String line; - private final Fonts.Style style; - private final double y; - - public Line(String line, Fonts.Style style, double y) { - this.line = line; - this.y = y; - this.style = style; - } - - public void render(RenderContext ctx, double ox, double oy) { - if (!line.isEmpty()) { - ctx.drawText(style, line, ox, oy + y); - } - } - } - - private static class Builder { - private final Size empty; - private double width = 0; - private double height = 0; - private List lines = Lists.newArrayList(); - - public Builder(Size empty) { - this.empty = empty; - } - - public Tooltip build(double x, double y) { - return new Tooltip(x, y, lines.toArray(new Line[lines.size()]), width, height); - } - - public void addLine(String line, Fonts.Style style, Size size, boolean addSep) { - if (!lines.isEmpty() && addSep) { - lines.add(new Line("", style, height)); - height += empty.h; - } - lines.add(new Line(line, style, height)); - width = Math.max(width, size.w); - height += size.h; - } - } - } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventPanel.java b/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventPanel.java index 40b016b03c..09b95ace14 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventPanel.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventPanel.java @@ -19,18 +19,15 @@ import static com.google.gapid.perfetto.views.Loading.drawLoading; import static com.google.gapid.perfetto.views.StyleConstants.SELECTION_THRESHOLD; import static com.google.gapid.perfetto.views.StyleConstants.colors; -import static com.google.gapid.util.MoreFutures.transform; import com.google.common.collect.Lists; import com.google.gapid.perfetto.TimeSpan; import com.google.gapid.perfetto.canvas.Area; import com.google.gapid.perfetto.canvas.Fonts; -import com.google.gapid.perfetto.canvas.Fonts.TextMeasurer; import com.google.gapid.perfetto.canvas.RenderContext; import com.google.gapid.perfetto.canvas.Size; import com.google.gapid.perfetto.models.GpuInfo; import com.google.gapid.perfetto.models.Selection; -import com.google.gapid.perfetto.models.Selection.CombiningBuilder; import com.google.gapid.perfetto.models.VulkanEventTrack; import org.eclipse.swt.SWT; @@ -41,7 +38,7 @@ public class VulkanEventPanel extends TrackPanel implements Selectable { private static final double ARROW_HEIGHT = 10; - private static final double ARROW_WIDTH = 10; + private static final double ARROW_WIDTH_MIN = 10; private static final double ARROW_TIP = 2; private static final double SLICE_Y = ARROW_HEIGHT; private static final double SLICE_HEIGHT = 25; @@ -88,7 +85,7 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou } TimeSpan visible = state.getVisibleTime(); - Selection selected = state.getSelection(Selection.Kind.VulkanEvent); + Selection selected = state.getSelection(Selection.Kind.VulkanEvent); List visibleSelected = Lists.newArrayList(); for (int i = 0; i < data.starts.length; i++) { @@ -130,26 +127,33 @@ protected void renderTrack(RenderContext ctx, Repainter repainter, double w, dou ctx.drawRect(rectStart, SLICE_Y + depth * SLICE_HEIGHT, rectWidth, SLICE_HEIGHT, BOUNDING_BOX_LINE_WIDTH); double mid = rectStart + rectWidth / 2; + double arrowWidth = Math.max(ARROW_WIDTH_MIN, + state.durationToDeltaPx(data.dists[index]) - rectWidth / 2); ctx.drawLine(mid, ARROW_TIP, mid, SLICE_Y); - ctx.drawLine(mid, ARROW_TIP, mid + ARROW_WIDTH, ARROW_TIP); - ctx.drawLine(mid + ARROW_WIDTH, ARROW_TIP, mid + ARROW_WIDTH - ARROW_TIP, 0); - ctx.drawLine(mid + ARROW_WIDTH, ARROW_TIP, mid + ARROW_WIDTH - ARROW_TIP, ARROW_TIP * 2); + ctx.drawLine(mid, ARROW_TIP, mid + arrowWidth, ARROW_TIP); + ctx.drawLine(mid + arrowWidth, ARROW_TIP, mid + arrowWidth - ARROW_TIP, 0); + ctx.drawLine(mid + arrowWidth, ARROW_TIP, mid + arrowWidth - ARROW_TIP, ARROW_TIP * 2); } if (hoveredName != null) { + double cardW = hoveredSize.w + 2 * HOVER_PADDING; + double cardX = mouseXpos + HOVER_MARGIN; + if (cardX >= w - cardW) { + cardX = mouseXpos - HOVER_MARGIN - cardW; + } ctx.setBackgroundColor(colors().hoverBackground); - ctx.fillRect( - mouseXpos + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + ctx.fillRect(cardX, mouseYpos, cardW, hoveredSize.h); ctx.setForegroundColor(colors().textMain); - ctx.drawText(Fonts.Style.Normal, hoveredName, - mouseXpos + HOVER_MARGIN + HOVER_PADDING, mouseYpos + HOVER_PADDING / 2); + ctx.drawText(Fonts.Style.Normal, hoveredName, cardX + HOVER_PADDING, + mouseYpos + HOVER_PADDING / 2); } }); } @Override - protected Hover onTrackMouseMove(TextMeasurer m, double x, double y, int mods) { - VulkanEventTrack.Data data = track.getData(state.toRequest(), onUiThread()); + protected Hover onTrackMouseMove( + Fonts.TextMeasurer m, Repainter repainter, double x, double y, int mods) { + VulkanEventTrack.Data data = track.getData(state.toRequest(), onUiThread(repainter)); if (data == null) { return Hover.NONE; } @@ -163,8 +167,10 @@ protected Hover onTrackMouseMove(TextMeasurer m, double x, double y, int mods) { mouseYpos = SLICE_Y + depth * SLICE_HEIGHT; long t = state.pxToTime(x); for (int i = 0; i < data.starts.length; i++) { - if (data.depths[i] == depth && data.starts[i] <= t && t <= data.ends[i]) { - hoveredName = data.names[i]; + long tStart = data.starts[i]; + long tEnd = data.ends[i]; + if (data.depths[i] == depth && tStart <= t && t <= tEnd) { + hoveredName = data.names[i] + " (" + TimeSpan.timeToString(tEnd - tStart) + ")"; hoveredSize = Size.vertCombine(HOVER_PADDING, HOVER_PADDING / 2, m.measure(Fonts.Style.Normal, hoveredName)); mouseYpos = Math.max(0, Math.min(mouseYpos - (hoveredSize.h - SLICE_HEIGHT) / 2, @@ -174,8 +180,12 @@ protected Hover onTrackMouseMove(TextMeasurer m, double x, double y, int mods) { return new Hover() { @Override public Area getRedraw() { - return new Area( - x + HOVER_MARGIN, mouseYpos, hoveredSize.w + 2 * HOVER_PADDING, hoveredSize.h); + double redrawW = HOVER_MARGIN + hoveredSize.w + 2 * HOVER_PADDING; + double redrawX = x; + if (redrawX >= state.getWidth() - redrawW) { + redrawX = x - redrawW; + } + return new Area(redrawX, mouseYpos, redrawW, hoveredSize.h); } @Override @@ -207,7 +217,7 @@ public boolean click() { } @Override - public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { + public void computeSelection(Selection.CombiningBuilder builder, Area area, TimeSpan ts) { int startDepth = (int)((area.y - SLICE_Y) / SLICE_HEIGHT); int endDepth = (int)((area.y + area.h - SLICE_Y) / SLICE_HEIGHT); if (startDepth == endDepth && area.h / SLICE_HEIGHT < SELECTION_THRESHOLD) { @@ -224,8 +234,7 @@ public void computeSelection(CombiningBuilder builder, Area area, TimeSpan ts) { } if (endDepth >= 0) { - builder.add(Selection.Kind.VulkanEvent, transform( - track.getSlices(ts, startDepth, endDepth), VulkanEventTrack.SlicesBuilder::new)); + builder.add(Selection.Kind.VulkanEvent, track.getSlices(ts, startDepth, endDepth)); } } } diff --git a/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventSelectionView.java deleted file mode 100644 index 704a46d1b6..0000000000 --- a/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventSelectionView.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2020 Google Inc. - * - * 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. - */ - -package com.google.gapid.perfetto.views; - -import static com.google.gapid.perfetto.TimeSpan.timeToString; -import static com.google.gapid.widgets.Widgets.createBoldLabel; -import static com.google.gapid.widgets.Widgets.createComposite; -import static com.google.gapid.widgets.Widgets.createLabel; -import static com.google.gapid.widgets.Widgets.withIndents; -import static com.google.gapid.widgets.Widgets.withLayoutData; -import static com.google.gapid.widgets.Widgets.withMargin; -import static com.google.gapid.widgets.Widgets.withSpans; - -import com.google.common.collect.Iterables; -import com.google.gapid.perfetto.models.VulkanEventTrack; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; - -/** - * Displays information about a selected Vulkan API event. - */ -public class VulkanEventSelectionView extends Composite { - private static final int PROPERTIES_PER_PANEL = 8; - private static final int PANEL_INDENT = 25; - - public VulkanEventSelectionView(Composite parent, State state, VulkanEventTrack.Slice slice) { - super(parent, SWT.NONE); - setLayout(withMargin(new GridLayout(2, false), 0, 0)); - - Composite main = withLayoutData(createComposite(this, new GridLayout(2, false)), - new GridData(SWT.LEFT, SWT.TOP, false, false)); - withLayoutData(createBoldLabel(main, "Slice:"), withSpans(new GridData(), 2, 1)); - - createLabel(main, "Name:"); - createLabel(main, slice.name); - - createLabel(main, "Time:"); - createLabel(main, timeToString(slice.time - state.getTraceTime().start)); - - createLabel(main, "Duration:"); - createLabel(main, timeToString(slice.dur)); - - createLabel(main, "Command Buffer:"); - createLabel(main, Long.toString(slice.commandBuffer)); - - createLabel(main, "Submission ID:"); - createLabel(main, Long.toString(slice.submissionId)); - - if (!slice.args.isEmpty()) { - String[] keys = Iterables.toArray(slice.args.keys(), String.class); - int panels = (keys.length + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; - Composite props = withLayoutData(createComposite(this, new GridLayout(2 * panels, false)), - withIndents(new GridData(SWT.LEFT, SWT.TOP, false, false), PANEL_INDENT, 0)); - withLayoutData(createBoldLabel(props, "Properties:"), - withSpans(new GridData(), 2 * panels, 1)); - - for (int i = 0; i < keys.length && i < PROPERTIES_PER_PANEL; i++) { - int cols = (keys.length - i + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; - for (int c = 0; c < cols; c++) { - withLayoutData(createLabel(props, keys[i + c * PROPERTIES_PER_PANEL] + ":"), - withIndents(new GridData(), (c == 0) ? 0 : PANEL_INDENT, 0)); - createLabel(props, String.valueOf(slice.args.get(keys[i + c * PROPERTIES_PER_PANEL]))); - } - if (cols != panels) { - withLayoutData(createLabel(props, ""), withSpans(new GridData(), 2 * (panels - cols), 1)); - } - } - } - } -} diff --git a/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventsSelectionView.java b/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventsSelectionView.java index 23d1ec2a39..4b28b4af33 100644 --- a/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventsSelectionView.java +++ b/gapic/src/main/com/google/gapid/perfetto/views/VulkanEventsSelectionView.java @@ -16,61 +16,107 @@ package com.google.gapid.perfetto.views; -import static com.google.gapid.widgets.Widgets.createTreeColumn; -import static com.google.gapid.widgets.Widgets.createTreeViewer; +import static com.google.gapid.perfetto.TimeSpan.timeToString; +import static com.google.gapid.widgets.Widgets.createBoldLabel; +import static com.google.gapid.widgets.Widgets.createComposite; +import static com.google.gapid.widgets.Widgets.createLabel; +import static com.google.gapid.widgets.Widgets.createSelectableLabel; +import static com.google.gapid.widgets.Widgets.createTableColumn; +import static com.google.gapid.widgets.Widgets.createTableViewer; import static com.google.gapid.widgets.Widgets.packColumns; +import static com.google.gapid.widgets.Widgets.withIndents; +import static com.google.gapid.widgets.Widgets.withLayoutData; +import static com.google.gapid.widgets.Widgets.withMargin; +import static com.google.gapid.widgets.Widgets.withSpans; +import com.google.common.collect.Iterables; import com.google.gapid.perfetto.models.VulkanEventTrack; -import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.LabelProvider; -import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; /** * Displays information about a list of selected vulkan API events. */ public class VulkanEventsSelectionView extends Composite { - public VulkanEventsSelectionView(Composite parent, VulkanEventTrack.Slices sel) { + private static final int PROPERTIES_PER_PANEL = 8; + private static final int PANEL_INDENT = 25; + + public VulkanEventsSelectionView(Composite parent, State state, VulkanEventTrack.Slices slices) { super(parent, SWT.NONE); - setLayout(new FillLayout()); + if (slices.getCount() == 1) { + setSingleSliceView(state, slices); + } else if (slices.getCount() > 1) { + setMultiSlicesView(slices); + } + } - TreeViewer viewer = createTreeViewer(this, SWT.NONE); - viewer.getTree().setHeaderVisible(true); - viewer.setContentProvider(new ITreeContentProvider() { - @Override - public Object[] getElements(Object inputElement) { - return sel.slices.toArray(); - } + private void setSingleSliceView(State state, VulkanEventTrack.Slices slice) { + setLayout(withMargin(new GridLayout(2, false), 0, 0)); - @Override - public boolean hasChildren(Object element) { - return false; - } + Composite main = withLayoutData(createComposite(this, new GridLayout(2, false)), + new GridData(SWT.LEFT, SWT.TOP, false, false)); + withLayoutData(createBoldLabel(main, "Slice:"), withSpans(new GridData(), 2, 1)); - @Override - public Object getParent(Object element) { - return null; - } + createLabel(main, "Name:"); + createSelectableLabel(main, slice.names.get(0)); + + createLabel(main, "Time:"); + createSelectableLabel(main, timeToString(slice.times.get(0) - state.getTraceTime().start)); + + createLabel(main, "Duration:"); + createSelectableLabel(main, timeToString(slice.durs.get(0))); - @Override - public Object[] getChildren(Object element) { - return null; + createLabel(main, "Command Buffer:"); + createSelectableLabel(main, String.format("0x%08X", slice.commandBuffers.get(0))); + + createLabel(main, "Submission ID:"); + createSelectableLabel(main, Long.toString(slice.submissionIds.get(0))); + + if (!slice.argSets.get(0).isEmpty()) { + String[] keys = Iterables.toArray(slice.argSets.get(0).keys(), String.class); + int panels = (keys.length + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; + Composite props = withLayoutData(createComposite(this, new GridLayout(2 * panels, false)), + withIndents(new GridData(SWT.LEFT, SWT.TOP, false, false), PANEL_INDENT, 0)); + withLayoutData(createBoldLabel(props, "Properties:"), + withSpans(new GridData(), 2 * panels, 1)); + + for (int i = 0; i < keys.length && i < PROPERTIES_PER_PANEL; i++) { + int cols = (keys.length - i + PROPERTIES_PER_PANEL - 1) / PROPERTIES_PER_PANEL; + for (int c = 0; c < cols; c++) { + withLayoutData(createSelectableLabel(props, keys[i + c * PROPERTIES_PER_PANEL] + ":"), + withIndents(new GridData(), (c == 0) ? 0 : PANEL_INDENT, 0)); + createSelectableLabel(props, String.valueOf(slice.argSets.get(0).get(keys[i + c * PROPERTIES_PER_PANEL]))); + } + if (cols != panels) { + withLayoutData(createLabel(props, ""), withSpans(new GridData(), 2 * (panels - cols), 1)); + } } - }); + } + } + + private void setMultiSlicesView(VulkanEventTrack.Slices slices) { + setLayout(new FillLayout()); + + TableViewer viewer = createTableViewer(this, SWT.NONE); + viewer.setContentProvider(new ArrayContentProvider()); viewer.setLabelProvider(new LabelProvider()); - createTreeColumn(viewer, "Slice ID", e -> Long.toString(n(e).id)); - createTreeColumn(viewer, "Start Time", e -> Long.toString(n(e).time)); - createTreeColumn(viewer, "Duration", e -> Long.toString(n(e).dur)); - createTreeColumn(viewer, "Event Name", e -> n(e).name); - createTreeColumn(viewer, "Submission ID", e -> Long.toString(n(e).submissionId)); - viewer.setInput(sel); - packColumns(viewer.getTree()); - } + createTableColumn(viewer, "Slice ID", e -> Long.toString(slices.ids.get((Integer)e))); + createTableColumn(viewer, "Start Time", e -> Long.toString(slices.times.get((Integer)e))); + createTableColumn(viewer, "Duration", e -> Long.toString(slices.durs.get((Integer)e))); + createTableColumn(viewer, "Event Name", e -> slices.names.get((Integer)e)); - protected static VulkanEventTrack.Slice n(Object o) { - return (VulkanEventTrack.Slice)o; + Integer[] rows = new Integer[slices.getCount()]; + for (int i = 0; i < rows.length; i++) { + rows[i] = i; + } + viewer.setInput(rows); + packColumns(viewer.getTable()); } } diff --git a/gapic/src/main/com/google/gapid/server/Client.java b/gapic/src/main/com/google/gapid/server/Client.java index 9e0c71359d..2146669c51 100644 --- a/gapic/src/main/com/google/gapid/server/Client.java +++ b/gapic/src/main/com/google/gapid/server/Client.java @@ -23,6 +23,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; +import com.google.gapid.models.ProfileExperiments; import com.google.gapid.models.Strings; import com.google.gapid.proto.log.Log; import com.google.gapid.proto.perfetto.Perfetto; @@ -33,19 +34,16 @@ import com.google.gapid.proto.service.Service.GetAvailableStringTablesRequest; import com.google.gapid.proto.service.Service.GetDevicesForReplayRequest; import com.google.gapid.proto.service.Service.GetDevicesRequest; -import com.google.gapid.proto.service.Service.GetFramebufferAttachmentRequest; import com.google.gapid.proto.service.Service.GetRequest; import com.google.gapid.proto.service.Service.GetServerInfoRequest; import com.google.gapid.proto.service.Service.GetStringTableRequest; import com.google.gapid.proto.service.Service.ImportCaptureRequest; import com.google.gapid.proto.service.Service.LoadCaptureRequest; -import com.google.gapid.proto.service.Service.Release; -import com.google.gapid.proto.service.Service.ReplaySettings; +import com.google.gapid.proto.service.Service.Releases; import com.google.gapid.proto.service.Service.SaveCaptureRequest; import com.google.gapid.proto.service.Service.ServerInfo; import com.google.gapid.proto.service.Service.SetRequest; import com.google.gapid.proto.service.Service.Value; -import com.google.gapid.proto.service.api.API; import com.google.gapid.proto.service.path.Path; import com.google.gapid.proto.stringtable.Stringtable; import com.google.gapid.rpc.RpcException; @@ -66,12 +64,18 @@ public class Client { private static final Logger LOG = Logger.getLogger(Client.class.getName()); - private final GapidClient client; + private GapidClient client; + + public Client() { /* Initialize an empty wrapper. */} public Client(GapidClient client) { this.client = client; } + public void setGapidClient(GapidClient client) { + this.client = client; + } + public ListenableFuture getSeverInfo() { return call(() -> "RPC->getServerInfo()", stack -> MoreFutures.transformAsync( @@ -79,13 +83,13 @@ public ListenableFuture getSeverInfo() { in -> immediateFuture(throwIfError(in.getInfo(), in.getError(), stack)))); } - public ListenableFuture checkForUpdates(boolean includeDevReleases) { + public ListenableFuture checkForUpdates(boolean includeDevReleases) { return call(() -> String.format("RPC->checkForUpdates(%b)", includeDevReleases), stack -> MoreFutures.transformAsync( client.checkForUpdates(CheckForUpdatesRequest.newBuilder() .setIncludeDevReleases(includeDevReleases) .build()), - in -> immediateFuture(throwIfError(in.getRelease(), in.getError(), stack)))); + in -> immediateFuture(throwIfError(in.getReleases(), in.getError(), stack)))); } public ListenableFuture get(Path.Any path, Path.Device device) { @@ -179,35 +183,13 @@ public ListenableFuture> getDevices() { .getListList()))); } - public ListenableFuture> getDevicesForReplay(Path.Capture capture) { + public ListenableFuture getDevicesForReplay(Path.Capture capture) { return call(() -> String.format("RPC->getDevicesForReplay(%s)", shortDebugString(capture)), stack -> MoreFutures.transformAsync( client.getDevicesForReplay(GetDevicesForReplayRequest.newBuilder() .setCapture(capture) .build()), - in -> immediateFuture(throwIfError(in.getDevices(), in.getError(), stack) - .getListList()))); - } - - public ListenableFuture getFramebufferAttachment(Path.Device device, - Path.Command after, API.FramebufferAttachment attachment, - Service.RenderSettings settings, Service.UsageHints hints, boolean disableReplayOptimization) { - return call( - () -> String.format("RPC->getFramebufferAttachment(%s, %s, %s, %s, %s)", - shortDebugString(device), shortDebugString(after), attachment, - shortDebugString(settings), shortDebugString(hints)), - stack -> MoreFutures.transformAsync( - client.getFramebufferAttachment(GetFramebufferAttachmentRequest.newBuilder() - .setReplaySettings( - ReplaySettings.newBuilder() - .setDevice(device) - .setDisableReplayOptimization(disableReplayOptimization)) - .setAfter(after) - .setAttachment(attachment) - .setSettings(settings) - .setHints(hints) - .build()), - in -> immediateFuture(throwIfError(in.getImage(), in.getError(), stack)))); + in -> immediateFuture(throwIfError(in.getDevices(), in.getError(), stack)))); } public ListenableFuture postEvent(Service.ClientInteraction interaction) { @@ -257,6 +239,39 @@ public ListenableFuture perfettoQuery(Path.Capture capture in -> immediateFuture(throwIfError(in.getResult(), in.getError(), stack)))); } + + public ListenableFuture profile(Path.Capture capture, Path.Device device, ProfileExperiments experiments, int loopCount) { + return call(() -> String.format( + "RPC->profile(%s, %s)", shortDebugString(capture), shortDebugString(device)), + stack -> MoreFutures.transformAsync( + client.profile(Service.GpuProfileRequest.newBuilder() + .setCapture(capture) + .setDevice(device) + .setExperiments(experiments.toProto()) + .setLoopCount(loopCount) + .build()), + in -> immediateFuture(throwIfError(in.getProfilingData(), in.getError(), stack)))); + } + + public ListenableFuture validateDevice(Path.Device device) { + return call(() -> String.format("RPC->validateDevice(%s)", shortDebugString(device)), + stack -> MoreFutures.transformAsync( + client.validateDevice(Service.ValidateDeviceRequest.newBuilder() + .setDevice(device) + .build()), + in -> immediateFuture(throwIfError(in.getResult(), in.getError(), stack)))); + } + + public ListenableFuture installApp(Path.Device device, String app) { + return call(() -> String.format("RPC->installApp(%s, %s)", shortDebugString(device), app), + stack -> MoreFutures.transformAsync( + client.installApp(Service.InstallAppRequest.newBuilder() + .setDevice(device) + .setApplication(app) + .build()), + in -> immediateFuture(throwIfError(null, in.getError(), stack)))); + } + public ListenableFuture streamLog(Consumer onLogMessage) { LOG.log(FINE, "RPC->getLogStream()"); return client.streamLog(onLogMessage); @@ -303,7 +318,7 @@ private static ListenableFuture call( return result; } - private static V throwIfError(V value, Service.Error err, Stack stack) throws RpcException { + public static V throwIfError(V value, Service.Error err, Stack stack) throws RpcException { switch (err.getErrCase()) { case ERR_NOT_SET: return value; diff --git a/gapic/src/main/com/google/gapid/server/GapiPaths.java b/gapic/src/main/com/google/gapid/server/GapiPaths.java index 94645b23fb..2342fe85ba 100644 --- a/gapic/src/main/com/google/gapid/server/GapiPaths.java +++ b/gapic/src/main/com/google/gapid/server/GapiPaths.java @@ -52,7 +52,7 @@ public final class GapiPaths { private static final String USER_HOME_GAPID_ROOT = "gapid"; private static final String GAPID_PKG_SUBDIR = "pkg"; private static final String GAPID_ROOT_ENV_VAR = "GAPID"; - private static final String RUNFILES_MANIFEST = "gapid.runfiles_manifest"; + private static final String RUNFILES_MANIFEST = "agi.runfiles_manifest"; private static GapiPaths tools; diff --git a/gapic/src/main/com/google/gapid/server/GapidClient.java b/gapic/src/main/com/google/gapid/server/GapidClient.java index 7a83271128..98801645ba 100644 --- a/gapic/src/main/com/google/gapid/server/GapidClient.java +++ b/gapic/src/main/com/google/gapid/server/GapidClient.java @@ -33,8 +33,6 @@ public ListenableFuture checkForUpdates( public ListenableFuture get(Service.GetRequest request); public ListenableFuture set(Service.SetRequest request); public ListenableFuture follow(Service.FollowRequest request); - public StreamSender profile( - StreamConsumer response); public ListenableFuture getPerformanceCounters( Service.GetPerformanceCountersRequest request); public ListenableFuture getProfile(Service.GetProfileRequest request); @@ -51,8 +49,6 @@ public ListenableFuture saveCapture( public ListenableFuture getDevices(Service.GetDevicesRequest request); public ListenableFuture getDevicesForReplay( Service.GetDevicesForReplayRequest request); - public ListenableFuture getFramebufferAttachment( - Service.GetFramebufferAttachmentRequest request); public ListenableFuture postClientEvent( Service.ClientEventRequest request); public ListenableFuture getTraceTargetTreeNode( @@ -61,6 +57,10 @@ public ListenableFuture updateSettings( Service.UpdateSettingsRequest request); public ListenableFuture perfettoQuery( Service.PerfettoQueryRequest request); + public ListenableFuture profile(Service.GpuProfileRequest request); + public ListenableFuture validateDevice( + Service.ValidateDeviceRequest request); + public ListenableFuture installApp(Service.InstallAppRequest request); public ListenableFuture streamLog(Consumer onLogMessage); public ListenableFuture streamStatus( diff --git a/gapic/src/main/com/google/gapid/server/GapidClientGrpc.java b/gapic/src/main/com/google/gapid/server/GapidClientGrpc.java index 8809a3aaca..f886652b3a 100644 --- a/gapic/src/main/com/google/gapid/server/GapidClientGrpc.java +++ b/gapic/src/main/com/google/gapid/server/GapidClientGrpc.java @@ -24,6 +24,10 @@ import com.google.gapid.proto.service.Service; import com.google.gapid.proto.service.Service.ClientEventRequest; import com.google.gapid.proto.service.Service.ClientEventResponse; +import com.google.gapid.proto.service.Service.GpuProfileRequest; +import com.google.gapid.proto.service.Service.GpuProfileResponse; +import com.google.gapid.proto.service.Service.InstallAppRequest; +import com.google.gapid.proto.service.Service.InstallAppResponse; import com.google.gapid.proto.service.Service.PerfettoQueryRequest; import com.google.gapid.proto.service.Service.PerfettoQueryResponse; import com.google.gapid.proto.service.Service.PingRequest; @@ -31,6 +35,8 @@ import com.google.gapid.proto.service.Service.TraceTargetTreeNodeResponse; import com.google.gapid.proto.service.Service.UpdateSettingsRequest; import com.google.gapid.proto.service.Service.UpdateSettingsResponse; +import com.google.gapid.proto.service.Service.ValidateDeviceRequest; +import com.google.gapid.proto.service.Service.ValidateDeviceResponse; import com.google.gapid.util.MoreFutures; import java.util.function.Consumer; @@ -81,13 +87,6 @@ public ListenableFuture follow(Service.FollowRequest req return client.follow(request); } - @Override - public GapidClient.StreamSender profile( - StreamConsumer response) { - StreamHandler handler = StreamHandler.wrap(response); - return Sender.wrap(handler.future, stub.profile(handler)); - } - @Override public ListenableFuture getPerformanceCounters( Service.GetPerformanceCountersRequest request) { @@ -142,12 +141,6 @@ public ListenableFuture getDevicesForReplay return client.getDevicesForReplay(request); } - @Override - public ListenableFuture getFramebufferAttachment( - Service.GetFramebufferAttachmentRequest request) { - return client.getFramebufferAttachment(request); - } - @Override public ListenableFuture postClientEvent(ClientEventRequest request) { return client.clientEvent(request); @@ -169,6 +162,21 @@ public ListenableFuture perfettoQuery(PerfettoQueryReques return client.perfettoQuery(request); } + @Override + public ListenableFuture profile(GpuProfileRequest request) { + return client.gpuProfile(request); + } + + @Override + public ListenableFuture validateDevice(ValidateDeviceRequest request) { + return client.validateDevice(request); + } + + @Override + public ListenableFuture installApp(InstallAppRequest request) { + return client.installApp(request); + } + @Override public ListenableFuture streamLog(Consumer onLogMessage) { StreamHandler handler = StreamHandler.wrap(onLogMessage); diff --git a/gapic/src/main/com/google/gapid/server/GapisConnection.java b/gapic/src/main/com/google/gapid/server/GapisConnection.java index dab0fde528..2b2e07e01c 100644 --- a/gapic/src/main/com/google/gapid/server/GapisConnection.java +++ b/gapic/src/main/com/google/gapid/server/GapisConnection.java @@ -17,6 +17,8 @@ import static io.grpc.ClientInterceptors.intercept; import static io.grpc.stub.MetadataUtils.newAttachHeadersInterceptor; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; import com.google.gapid.proto.service.GapidGrpc; @@ -25,6 +27,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; import io.grpc.Channel; import io.grpc.ManagedChannel; @@ -86,6 +89,7 @@ private static class GRpcGapisConnection extends GapisConnection { private final ManagedChannel baseChannel; private final Channel channel; private final int heartbeatRateMS; + private Thread heartbeat; public GRpcGapisConnection( CloseListener listener, String target, String authToken, int heartbeatRateMS) { @@ -122,7 +126,11 @@ public GapidClient createGapidClient(boolean caching) throws IOException { GapidClient client = caching ? new GapidClientCache(futureStub, stub) : new GapidClientGrpc(futureStub, stub); if (heartbeatRateMS > 0) { - new Heartbeat(client, heartbeatRateMS).start(); + if (heartbeat != null) { + heartbeat.interrupt(); + } + heartbeat = new Heartbeat(client, heartbeatRateMS); + heartbeat.start(); } return client; } @@ -130,6 +138,10 @@ public GapidClient createGapidClient(boolean caching) throws IOException { @Override public void close() { baseChannel.shutdown(); + if (heartbeat != null) { + heartbeat.interrupt(); + heartbeat = null; + } super.close(); } @@ -138,8 +150,13 @@ public void close() { * the server from exiting due to the --idle-timeout. */ protected static class Heartbeat extends Thread { + private static final Logger LOG = Logger.getLogger(Heartbeat.class.getName()); + private static final long ERROR_LOG_INTERVAL = TimeUnit.SECONDS.toMillis(10); + private final GapidClient client; private final int rateMS; + private int errorCount; + private long timeLastErrorLogged; Heartbeat(GapidClient client, int rateMS) { this.client = client; @@ -150,10 +167,22 @@ protected static class Heartbeat extends Thread { public void run() { while (true) { try { - client.ping().get(rateMS, TimeUnit.MILLISECONDS); + try { + client.ping().get(rateMS, TimeUnit.MILLISECONDS); + } catch (ExecutionException | TimeoutException e) { + errorCount++; + if (System.currentTimeMillis() - timeLastErrorLogged > ERROR_LOG_INTERVAL) { + LOG.log(WARNING, "Heartbeat ping has failed " + errorCount + "x since startup", e); + timeLastErrorLogged = System.currentTimeMillis(); + } + // Continue sending pings, since this a "I'm still here!" kind of heartbeat, and not an + // "are you still there?" kind. + } + Thread.sleep(rateMS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - return; // If the connection failed, the error will appear on another thread. + } catch (InterruptedException e) { + LOG.log(INFO, "Heartbeat exiting due to interruption: " + e); + return; } } } diff --git a/gapic/src/main/com/google/gapid/server/GapisProcess.java b/gapic/src/main/com/google/gapid/server/GapisProcess.java index b6e22381ae..d59b2ca480 100644 --- a/gapic/src/main/com/google/gapid/server/GapisProcess.java +++ b/gapic/src/main/com/google/gapid/server/GapisProcess.java @@ -21,6 +21,7 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Settings; +import com.google.gapid.util.Experimental; import com.google.gapid.util.Flags; import com.google.gapid.util.Flags.Flag; import com.google.gapid.util.Logging; @@ -96,6 +97,9 @@ protected Exception prepare(ProcessBuilder pb) throws GapiPaths.MissingToolsExce args.add(settings.preferences().getAnalyticsClientId()); } + // Append all experimental flags if any is enabled. + args.addAll(Experimental.getGapisFlags(settings.preferences().getEnableAllExperimentalFeatures())); + File logDir = Logging.getLogDir(); if (logDir != null) { args.add("-log-file"); diff --git a/gapic/src/main/com/google/gapid/settings.proto b/gapic/src/main/com/google/gapid/settings.proto index 183e3aba81..469e559956 100644 --- a/gapic/src/main/com/google/gapid/settings.proto +++ b/gapic/src/main/com/google/gapid/settings.proto @@ -14,11 +14,14 @@ syntax = "proto3"; -import "protos/perfetto/config/perfetto_config.proto"; +import "protos/perfetto/config/trace_config.proto"; option java_package = "com.google.gapid.proto"; option java_outer_classname = "SettingsProto"; +import "core/os/device/device.proto"; +import "gapis/service/service.proto"; + message Point { int32 x = 1; int32 y = 2; @@ -49,9 +52,12 @@ message Preferences { bool update_available = 5; // milliseconds since midnight, January 1, 1970 UTC. int64 last_check_for_updates = 6; + service.Releases.ANGLERelease latest_angle_release = 11; string analytics_client_id = 7; // Empty (default) means do not track. bool disable_replay_optimization = 8; bool report_crashes = 9; + bool enable_all_experimental_features = 10; + bool use_frame_looping = 12; } message UI { @@ -66,6 +72,28 @@ message UI { bool show_vsync = 3; } Perfetto perfetto = 2; + + message FramebufferPicker { + bool enabled = 1; + } + FramebufferPicker framebuffer_picker = 3; + + message PerformancePreset { + string preset_name = 1; + string device_name = 2; + repeated int32 counter_ids = 3; + } + repeated PerformancePreset performance_presets = 4; + + message CommandFilter { + bool show_host_commands = 1; + bool show_submit_info_nodes = 2; + bool show_sync_commands = 3; + bool show_begin_end_commands = 4; + } + CommandFilter command_filter = 5; + + double command_splitter_ratio = 6; } message Trace { @@ -86,7 +114,8 @@ message Trace { string device_serial = 1; string device_name = 2; string type = 3; - string api = 4; + bool last_frame_trace_was_vulkan = 21; + string api = 4 [deprecated = true]; // Kept for de-serialization. string uri = 5; string arguments = 6; string cwd = 7; @@ -94,13 +123,15 @@ message Trace { reserved 9; reserved 10; bool without_buffering = 11; - bool hide_unknown_extensions = 12; + reserved 12; bool clear_cache = 13; - bool disable_pcs = 14; + reserved 14; string out_dir = 15; string friendly_name = 16; Duration gfx_duration = 17; Duration profile_duration = 18; + string process_name = 19; + bool load_validation_layer = 20; } message Perfetto { @@ -112,13 +143,18 @@ message Perfetto { } CPU cpu = 1; + message GPUCounters { + repeated int32 counter_ids = 1; + } + message GPU { bool enabled = 1; bool slices = 2; bool counters = 3; int32 counter_rate = 4; - repeated int32 counter_ids = 5; + reserved 5; bool surface_flinger = 6; + map counters_by_gpu = 7; } GPU gpu = 2; @@ -131,6 +167,8 @@ message Perfetto { message Battery { bool enabled = 1; int32 rate = 2; + bool collectPowerRail = 3; + bool collectEnergyBreakdown = 4; } Battery battery = 4; @@ -156,10 +194,16 @@ message Perfetto { } Vulkan vulkan = 5; + bool force_tracing_to_file = 8; + perfetto.protos.TraceConfig custom_config = 6; bool use_custom = 7; } +message FuchsiaTracing { + repeated string categories = 1; +} + message Settings { int32 version = 1; Window window = 2; @@ -169,4 +213,27 @@ message Settings { UI ui = 6; Trace trace = 7; Perfetto perfetto = 8; + DeviceValidation device_validation = 9; + FuchsiaTracing fuchsia_tracing = 10; +} + +// DeviceValidation contains the list of validated devices. +message DeviceValidation { + message Device { + string serial = 1; + device.OS os = 2; + // The pre-release driver APK version code + string version = 3; + } + + message Result { + bool passed = 1; + } + + message ValidationEntry { + Device device = 1; + Result result = 2; + int64 last_seen = 3; + } + repeated ValidationEntry validation_entries = 1; } diff --git a/gapic/src/main/com/google/gapid/util/Arrays.java b/gapic/src/main/com/google/gapid/util/Arrays.java index 2cdf98fa08..64187596ba 100644 --- a/gapic/src/main/com/google/gapid/util/Arrays.java +++ b/gapic/src/main/com/google/gapid/util/Arrays.java @@ -25,4 +25,14 @@ private Arrays() { public static T last(T[] array) { return (array == null || array.length == 0) ? null : array[array.length - 1]; } + + public static T getOrDefault(T[] array, int idx, T dflt) { + return array == null || idx >= array.length ? dflt : array[idx]; + } + + public static long[] filled(long[] array, long val) { + // No generic pattern here because want a long[] array rather than Long[] array. + java.util.Arrays.fill(array, val); + return array; + } } diff --git a/gapic/src/main/com/google/gapid/util/CenteringStackLayout.java b/gapic/src/main/com/google/gapid/util/CenteringStackLayout.java new file mode 100644 index 0000000000..ed4e643645 --- /dev/null +++ b/gapic/src/main/com/google/gapid/util/CenteringStackLayout.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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. + */ +package com.google.gapid.util; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StackLayout; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * A {@link StackLayout} that vertically centers the control currently being displayed. + */ +public class CenteringStackLayout extends StackLayout { + @Override + protected void layout(Composite composite, boolean flushCache) { + Rectangle rect = composite.getClientArea(); + rect.x += marginWidth; + rect.y += marginHeight; + rect.width -= 2 * marginWidth; + rect.height -= 2 * marginHeight; + for (Control element : composite.getChildren()) { + Point size = element.computeSize(rect.width, SWT.DEFAULT); + Rectangle bounds = rect; + if (size.y < rect.height) { + bounds = new Rectangle(rect.x, rect.y + (rect.height - size.y) / 2, rect.width, size.y); + } + element.setBounds(bounds); + element.setVisible(element == topControl); + } + } +} diff --git a/gapic/src/main/com/google/gapid/util/Experimental.java b/gapic/src/main/com/google/gapid/util/Experimental.java new file mode 100644 index 0000000000..4de6e6a976 --- /dev/null +++ b/gapic/src/main/com/google/gapid/util/Experimental.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2020 Google Inc. + * + * 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. + */ +package com.google.gapid.util; + +import com.google.common.collect.Lists; +import com.google.gapid.models.Settings; +import com.google.gapid.util.Flags.Flag; + +import java.util.List; + +/** + * Command line flag definition for experimental features. + */ +public class Experimental { + public static final Flag enableAll = Flags.value("experimental-enable-all", false, + "Enable all experimental features. " + + "Features turned on by this flag are all unstable and under development."); + + public static final Flag enableProfileExperiments = Flags.value("experimental-enable-profile-experiments", + false, "Enable Profile Experiments."); + + public static final Flag enableUnstableFeatures = Flags.value("experimental-enable-unstable-features", + false, "Enable various unstable features that are not ready for use yet."); + + public static List getGapisFlags(boolean enableAllExperimentalFeatures) { + List args = Lists.newArrayList(); + // The --experimental-enable-all flag is a sugar flag from the UI. GAPIS knows nothing about it. + if (enableAllExperimentalFeatures || Experimental.enableAll.get()) { + // None at this time. + } else { + // None at this time. + } + return args; + } + + public static boolean enableProfileExperiments(Settings settings) { + return settings.preferences().getEnableAllExperimentalFeatures() || + enableAll.get() || enableProfileExperiments.get(); + } + + public static boolean enableUnstableFeatures(Settings settings) { + return settings.preferences().getEnableAllExperimentalFeatures() || + enableAll.get() || enableUnstableFeatures.get(); + } +} diff --git a/gapic/src/main/com/google/gapid/util/Flags.java b/gapic/src/main/com/google/gapid/util/Flags.java index 497b51293e..6a3d82dac2 100644 --- a/gapic/src/main/com/google/gapid/util/Flags.java +++ b/gapic/src/main/com/google/gapid/util/Flags.java @@ -39,7 +39,7 @@ public class Flags { public static final Flag help = value("help", false, "Print help information."); public static final Flag fullHelp = value("fullhelp", false, "Print help information incuding hidden flags.", true); - public static final Flag version = value("version", false, "Print GAPID version."); + public static final Flag version = value("version", false, "Print AGI version."); private static boolean initialized = false; @@ -130,7 +130,7 @@ private static boolean isBooleanFlag(Flag flag) { } private static void printVersion(PrintStream out) { - out.println("GAPID version " + GAPID_VERSION); + out.println("AGI version " + GAPID_VERSION); } private static void printHelp(PrintStream out, Flag[] flags, boolean full) { diff --git a/gapic/src/main/com/google/gapid/util/GapidVersion.java.in b/gapic/src/main/com/google/gapid/util/GapidVersion.java.in index e9d77267ca..4bd97a2c14 100644 --- a/gapic/src/main/com/google/gapid/util/GapidVersion.java.in +++ b/gapic/src/main/com/google/gapid/util/GapidVersion.java.in @@ -17,7 +17,7 @@ package com.google.gapid.util; /** * Current version specifier. */ -@javax.annotation.Generated("by GAPID bazel") +@javax.annotation.processing.Generated("by AGI bazel") public interface GapidVersion { - public static final Version GAPID_VERSION = new Version(@GAPID_VERSION_MAJOR@, @GAPID_VERSION_MINOR@, @GAPID_VERSION_POINT@, "@GAPID_BUILD_SHA@"); + public static final Version GAPID_VERSION = new Version(@AGI_VERSION_MAJOR@, @AGI_VERSION_MINOR@, @AGI_VERSION_POINT@, "@AGI_BUILD_SHA@", @AGI_BUILD_YEAR@); } diff --git a/gapic/src/main/com/google/gapid/util/Logging.java b/gapic/src/main/com/google/gapid/util/Logging.java index 5e74b94186..26534261f2 100644 --- a/gapic/src/main/com/google/gapid/util/Logging.java +++ b/gapic/src/main/com/google/gapid/util/Logging.java @@ -114,6 +114,9 @@ public interface Listener { private static final Listener NULL_LISTENER = (m) -> { /* do nothing */ }; + private static final String SERVER_LOG_FILE = "gapis.log"; + private static final String CLIENT_LOG_FILE = "gapic.log"; + private static Listener listener = NULL_LISTENER; private static long timeOfLastThrottledMessage = 0; @@ -134,24 +137,52 @@ public static void init() { handler.setLevel(Level.ALL); rootLogger.addHandler(handler); + initFileHandler(rootLogger); + + Logger.getLogger("com.google.gapid").setLevel(logLevel.get().level); + } + + private static void initFileHandler(Logger rootLogger) { if (!logDir.get().isEmpty()) { - try { - FileHandler fileHandler = new FileHandler(logDir.get() + File.separator + "gapic.log"); - fileHandler.setFormatter(new LogFormatter()); - fileHandler.setLevel(Level.ALL); - rootLogger.addHandler(fileHandler); - } catch (IOException e) { - // Ignore. + File dir = new File(logDir.get()); + dir.mkdirs(); + + File file = new File(dir, CLIENT_LOG_FILE); + for (int i = 0; i < 10; i++) { + if (!file.exists() || file.canWrite()) { + try { + FileHandler fileHandler = new FileHandler(file.getAbsolutePath()); + fileHandler.setFormatter(new LogFormatter()); + fileHandler.setLevel(Level.ALL); + rootLogger.addHandler(fileHandler); + return; + } catch (IOException e) { + System.err.println("Failed to create log file " + file + ":"); + e.printStackTrace(System.err); + } + } + + // Try a different name next. + file = new File(dir, "gapic-" + i + ".log"); } - } - Logger.getLogger("com.google.gapid").setLevel(logLevel.get().level); + // Give up. + System.err.println("Failed to create log file in " + logDir.get()); + } } public static File getLogDir() { return logDir.get().isEmpty() ? null : new File(logDir.get()); } + public static File getServerLogFile() { + return new File(getLogDir(), SERVER_LOG_FILE); + } + + public static File getClientLogFile() { + return new File(getLogDir(), CLIENT_LOG_FILE); + } + public static String getGapisLogLevel() { return (gapisLogLevel.isSpecified() ? gapisLogLevel.get() : logLevel.get()).gapisLevel; } diff --git a/gapic/src/main/com/google/gapid/util/LoggingCallback.java b/gapic/src/main/com/google/gapid/util/LoggingCallback.java index 6b94c414b0..9c1909564e 100644 --- a/gapic/src/main/com/google/gapid/util/LoggingCallback.java +++ b/gapic/src/main/com/google/gapid/util/LoggingCallback.java @@ -17,6 +17,7 @@ import com.google.common.util.concurrent.FutureCallback; +import java.util.concurrent.CancellationException; import java.util.logging.Level; import java.util.logging.Logger; @@ -25,13 +26,21 @@ */ public abstract class LoggingCallback implements FutureCallback { private final Logger log; + private final boolean ignoreCancel; public LoggingCallback(Logger log) { + this(log, false); + } + + public LoggingCallback(Logger log, boolean ignoreCancel) { this.log = log; + this.ignoreCancel = ignoreCancel; } @Override public void onFailure(Throwable t) { - log.log(Level.WARNING, "Unexpected and unhandled exception in async processing.", t); + if (!ignoreCancel || !(t instanceof CancellationException)) { + log.log(Level.WARNING, "Unexpected and unhandled exception in async processing.", t); + } } } diff --git a/gapic/src/main/com/google/gapid/util/MacApplication.java b/gapic/src/main/com/google/gapid/util/MacApplication.java index c21333ecc7..190034cd25 100644 --- a/gapic/src/main/com/google/gapid/util/MacApplication.java +++ b/gapic/src/main/com/google/gapid/util/MacApplication.java @@ -15,25 +15,44 @@ */ package com.google.gapid.util; +import static java.util.logging.Level.INFO; + import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import java.util.function.Consumer; +import java.util.logging.Logger; /** * Special handling for OSX application menus. */ public class MacApplication { + private static final Logger LOG = Logger.getLogger(MacApplication.class.getName()); + + private static Consumer onOpen = null; + private static String documentToOpen; + private MacApplication() { } + public static void listenForOpenDocument(Display display) { + display.addListener(SWT.OpenDocument, e -> { + LOG.log(INFO, "OpenDocument Event: " + e); + if (onOpen != null) { + onOpen.accept(e.text); + } else { + documentToOpen = e.text; + } + }); + } + /** * Initializes the OSX application menus. */ public static void init( - Display display, Runnable onAbout, Runnable onSettings, Consumer onOpen) { + Display display, Runnable onAbout, Runnable onSettings, Consumer newOnOpen) { Menu menu = display.getSystemMenu(); if (menu == null) { return; @@ -50,6 +69,9 @@ public static void init( } } - display.addListener(SWT.OpenDocument, e -> onOpen.accept(e.text)); + onOpen = newOnOpen; + if (documentToOpen != null) { + onOpen.accept(documentToOpen); + } } } diff --git a/gapic/src/main/com/google/gapid/util/MemoryBoxes.java b/gapic/src/main/com/google/gapid/util/MemoryBoxes.java index 4a50a04ef7..3ea5c83986 100644 --- a/gapic/src/main/com/google/gapid/util/MemoryBoxes.java +++ b/gapic/src/main/com/google/gapid/util/MemoryBoxes.java @@ -39,13 +39,23 @@ public static String format(MemoryBox.Value value, long rootAddress) { sb.append(toPointerString(value.getPointer().getAddress())); break; case SLICE: - sb.append("Array base: ").append(toPointerString(rootAddress)); + if (value.getSlice().hasRepresentation()) { + Pods.append(sb, value.getSlice().getRepresentation()); + sb.append(String.format(" (%s)", toPointerString(rootAddress))); + } else { + sb.append("Array base: ").append(toPointerString(rootAddress)); + } break; case STRUCT: // No more value related info to expose. break; case ARRAY: + if (value.getArray().hasRepresentation()) { + Pods.append(sb, value.getArray().getRepresentation()); + sb.append(String.format(" (%s)", toPointerString(rootAddress))); + } else { sb.append("Array base: ").append(toPointerString(rootAddress)); + } break; case NULL: sb.append("(nil)"); diff --git a/gapic/src/main/com/google/gapid/util/Messages.java b/gapic/src/main/com/google/gapid/util/Messages.java index 6e2434e2f3..ccffe971e1 100644 --- a/gapic/src/main/com/google/gapid/util/Messages.java +++ b/gapic/src/main/com/google/gapid/util/Messages.java @@ -18,8 +18,9 @@ import static com.google.gapid.util.GapidVersion.GAPID_VERSION; public interface Messages { - public static final String WINDOW_TITLE = "Graphics API Debugger"; + public static final String WINDOW_TITLE = "Android GPU Inspector"; public static final String LOADING_CAPTURE = "Loading capture..."; + public static final String LOADING_PROFILE = "Profiling replay..."; public static final String CAPTURE_LOAD_FAILURE = "Failed to load capture."; public static final String NO_FRAMES_IN_CONTEXT = "No frames in selected context."; public static final String SELECT_COMMAND = "Select a frame or command."; @@ -32,6 +33,7 @@ public interface Messages { public static final String SELECT_SHADER = "Select a shader."; public static final String SELECT_PROGRAM = "Select a program."; public static final String NO_IMAGE_DATA = "No image data available at this point in the trace."; + public static final String NO_SHADERS = "No shaders have been created by this point."; public static final String NO_TEXTURES = "No textures have been created by this point."; public static final String VIEW_DETAILS = "View Details"; public static final String LICENSES = "Licenses"; @@ -47,6 +49,8 @@ public interface Messages { public static final String MEMORY_STRUCT_TAB_TEXT = "Struct"; public static final String CAPTURE_TRACE_GRAPHICS = "Capture Graphics Trace"; public static final String CAPTURE_TRACE_PERFETTO = "Capture System Profile"; + public static final String CAPTURE_TRACE_FUCHSIA = "Capture Fuchsia System Profile"; + public static final String CAPTURE_TRACE_DEFAULT = "Capture A New Trace"; public static final String CAPTURING_TRACE = "Capturing..."; public static final String CAPTURE_DIRECTORY = "Capture output directory..."; public static final String CAPTURE_EXECUTABLE = "Executable to trace..."; @@ -55,16 +59,20 @@ public interface Messages { public static final String SELECT_ACTIVITY = "Select an Application to Trace"; public static final String WELCOME_TITLE = WINDOW_TITLE + " - Welcome"; public static final String WELCOME_SUBTITLE = "Get started with " + WINDOW_TITLE; - public static final String WELCOME_TEXT = "GAPID allows you to inspect, tweak, and replay calls" + + public static final String WELCOME_TEXT = "AGI allows you to inspect, tweak, and replay calls" + " from an application to a\ngraphics API. To begin, let us know where adb is located on" + " your computer."; public static final String WELCOME_BUTTON = "Get Started"; public static final String ANALYTICS_OPTION = - "Help improve GAPID by sending usage statistics to Google"; + "Help improve Android GPU Inspector by sending usage statistics to Google"; public static final String CRASH_REPORTING_OPTION = - "Help GAPID identify issues by sending crash reports to Google"; - public static final String UPDATE_CHECK_OPTION = "Automatically check for GAPID updates (please restart GAPID to force an update check)"; + "Help Android GPU Inspector identify issues by sending crash reports to Google"; + public static final String UPDATE_CHECK_OPTION = "Automatically check for AGI updates"; public static final String UPDATE_CHECK_DEV_RELEASE_OPTION = "Include unstable developer releases"; + public static final String UPDATE_CHECK_TITLE = "Update Check"; + public static final String UPDATE_CHECK_NO_UPDATE = + "No new version of AGI was found. You are up-to-date!"; + public static final String UPDATE_CHECK_UPDATE = "A new version of AGI is available."; public static final String PRIVACY_POLICY = "Google's APIs Terms of Service and Privacy Policy" + " govern your use of this application."; @@ -72,8 +80,12 @@ public interface Messages { public static final String SETTINGS_TITLE = "Modify Settings"; public static final String ERROR_MESSAGE = "The application encountered an error:\n%s\n\nPlease check the logs for details."; + public static final String SERVER_ERROR_MESSAGE = + "\nThe server has exited with an error code of: %s \n" + + "Most functions in AGI are unavailable without a server. \n" + + "You can restart the server, exit AGI, or close the dialog to continue without a server."; public static final String BUG_BODY = - "GAPID Version: " + GAPID_VERSION.toString() + "\n" + + "AGI Version: " + GAPID_VERSION.toString() + "\n" + "OS: " + OS.name + " " + OS.arch + "\n\n" + "Please provide detailed steps that led to the error and copy-paste the stack trace.\n" + "Extra details from the logs and the trace file would be extra helpful.\n\n"; @@ -81,6 +93,17 @@ public interface Messages { "Failed to create an OpenGL context. OpenGL is required to use this application."; public static final String GEO_SEMANTICS_TITLE = "Vertex Semantics"; public static final String GEO_SEMANTICS_HINT = "Manually configure the vertex stream semantics:"; - public static final String QUERY_VIEW_WINDOW_TITLE = "GAPID - Query Shell"; + public static final String QUERY_VIEW_WINDOW_TITLE = "AGI - Query Shell"; + public static final String TRACE_METADATA_VIEW_TITLE = "Trace Info"; public static final String KEYBOARD_MOUSE_HELP_TITLE = "Keyboard/Mouse Shortcut Help"; + public static final String PROFILE_NO_SLICES = + "GPU Profiling is not supported on this device or for this capture"; + public static final String SELECT_DEVICE_TITLE = + "Select Replay Device"; + public static final String SELECT_DEVICE_NO_COMPATIBLE_FOUND = + "No compatible replay device found. Please plug in a compatible device and refresh the list."; + public static final String SELECT_DEVICE_REFRESH_TABLE = "Refresh device tables"; + public static final String SELECT_DEVICE_TABLE_REFRESHING = "Refreshing devices..."; + public static final String VALIDATION_FAILED_LANDING_PAGE = "Why is my device not supported?"; + public static final String INSTALL_ANGLE_TITLE = "Downloading and Installing ANGLE..."; } diff --git a/gapic/src/main/com/google/gapid/util/MoreFutures.java b/gapic/src/main/com/google/gapid/util/MoreFutures.java index 3c0f48dcf6..13f553196e 100644 --- a/gapic/src/main/com/google/gapid/util/MoreFutures.java +++ b/gapic/src/main/com/google/gapid/util/MoreFutures.java @@ -50,13 +50,24 @@ public static ListenableFuture transformAsync( return Futures.transformAsync(input, function, Scheduler.EXECUTOR); } - public static void logFailure(Logger log, ListenableFuture future) { - addCallback(future, new LoggingCallback(log) { + public static ListenableFuture logFailure(Logger log, ListenableFuture future) { + return logFailure(log, future, false); + } + + public static ListenableFuture logFailureIgnoringCancel( + Logger log, ListenableFuture future) { + return logFailure(log, future, true); + } + + private static ListenableFuture logFailure( + Logger log, ListenableFuture future, boolean ignoreCancel) { + addCallback(future, new LoggingCallback(log, ignoreCancel) { @Override public void onSuccess(Object result) { // Ignore. } }); + return future; } public static ListenableFuture combine( diff --git a/gapic/src/main/com/google/gapid/util/Paths.java b/gapic/src/main/com/google/gapid/util/Paths.java index 7658951115..379dae7039 100644 --- a/gapic/src/main/com/google/gapid/util/Paths.java +++ b/gapic/src/main/com/google/gapid/util/Paths.java @@ -18,8 +18,9 @@ import com.google.common.collect.Lists; import com.google.common.primitives.UnsignedLongs; import com.google.gapid.image.Images; -import com.google.gapid.models.ApiContext.FilteringContext; import com.google.gapid.models.CommandStream.CommandIndex; +import com.google.gapid.models.Settings; +import com.google.gapid.proto.SettingsProto; import com.google.gapid.proto.device.Device; import com.google.gapid.proto.image.Image; import com.google.gapid.proto.service.Service; @@ -29,6 +30,7 @@ import com.google.protobuf.Message; import java.util.LinkedList; +import java.util.List; import java.util.function.Predicate; /** @@ -77,32 +79,25 @@ public static Path.Any capture(Path.ID id, boolean excludeObservations) { .build(); } - public static Path.Any context(Path.Context ctx) { - return Path.Any.newBuilder() - .setContext(ctx) - .build(); - } - public static Path.Any command(Path.Command command) { return Path.Any.newBuilder() .setCommand(command) .build(); } - public static Path.Any commandTree(Path.Capture capture, FilteringContext context) { - return Path.Any.newBuilder().setCommandTree( - context.commandTree(Path.CommandTree.newBuilder()) - .setCapture(capture) - .setMaxChildren(2000) - .setMaxNeighbours(20)) - .build(); - } - - public static Path.Any events(Path.Capture capture, FilteringContext context) { + public static Path.Any commandTree(Path.Capture capture, CommandFilter filter) { return Path.Any.newBuilder() - .setEvents(context.events(Path.Events.newBuilder()) - .setCapture(capture) - .setLastInFrame(true)) + .setCommandTree(filter.toProto( + Path.CommandTree.newBuilder() + .setCapture(capture) + .setGroupByFrame(true) + .setGroupByDrawCall(true) + .setGroupByTransformFeedback(true) + .setGroupByUserMarkers(true) + .setGroupBySubmission(true) + .setAllowIncompleteFrame(true) + .setMaxChildren(2000) + .setMaxNeighbours(20))) .build(); } @@ -118,11 +113,12 @@ public static Path.Any commandTree(Path.CommandTreeNode.Builder node) { .build(); } - public static Path.Any commandTree(Path.ID tree, Path.Command command) { + public static Path.Any commandTreeNodeForCommand(Path.ID tree, Path.Command command, boolean preferGroup) { return Path.Any.newBuilder() .setCommandTreeNodeForCommand(Path.CommandTreeNodeForCommand.newBuilder() .setTree(tree) - .setCommand(command)) + .setCommand(command) + .setPreferGroup(preferGroup)) .build(); } @@ -135,14 +131,14 @@ public static Path.State stateAfter(Path.Command command) { .build(); } - public static Path.Any stateTree(CommandIndex command, FilteringContext context) { + public static Path.Any stateTree(CommandIndex command) { if (command == null) { return null; } return Path.Any.newBuilder() - .setStateTree(context.stateTree(Path.StateTree.newBuilder() + .setStateTree(Path.StateTree.newBuilder() .setState(stateAfter(command.getCommand())) - .setArrayGroupSize(2000))) + .setArrayGroupSize(2000)) .build(); } @@ -232,16 +228,62 @@ public static Path.Any resourceAfter(CommandIndex command, Path.ID id) { .build(); } + public static Path.Any resourcesAfter(CommandIndex command, Path.ResourceType type) { + if (command == null || type == null) { + return null; + } + return Path.Any.newBuilder() + .setMultiResourceData(Path.MultiResourceData.newBuilder() + .setAfter(command.getCommand()) + .setAll(true) + .setType(type)) + .build(); + } + + public static Path.Any resourceExtrasAfter(CommandIndex command, Path.ID id) { + if (command == null || id == null) { + return null; + } + return Path.Any.newBuilder() + .setResourceExtras(Path.ResourceExtras.newBuilder() + .setAfter(command.getCommand()) + .setID(id)) + .build(); + } + public static Path.Any pipelinesAfter(CommandIndex command) { if (command == null) { return null; } return Path.Any.newBuilder() .setPipelines(Path.Pipelines.newBuilder() + .setCommandTreeNode(command.getNode())) + .build(); + } + + public static Path.Any framebufferAttachmentsAfter(CommandIndex command) { + if (command == null) { + return null; + } + return Path.Any.newBuilder() + .setFramebufferAttachments(Path.FramebufferAttachments.newBuilder() .setAfter(command.getCommand())) .build(); } + public static Path.Any framebufferAttachmentAfter(CommandIndex command, int index, Path.RenderSettings settings, Path.UsageHints hints) { + if (command == null) { + return null; + } + return Path.Any.newBuilder() + .setFramebufferAttachment(Path.FramebufferAttachment.newBuilder() + .setAfter(command.getCommand()) + .setIndex(index) + .setRenderSettings(settings) + .setHints(hints)) + .build(); + } + public static final Path.MeshOptions NODATA_MESH_OPTIONS = Path.MeshOptions.newBuilder() .setExcludeData(true) .build(); @@ -320,6 +362,15 @@ public static Path.Any imageData(Path.ResourceData resource, Image.Format format .build(); } + public static Path.RenderSettings renderSettings(int maxWidth, int maxHeight, Path.DrawMode drawMode, boolean disableReplayOptiimization) { + return Path.RenderSettings.newBuilder() + .setMaxWidth(maxWidth) + .setMaxHeight(maxHeight) + .setDrawMode(drawMode) + .setDisableReplayOptimization(disableReplayOptiimization) + .build(); + } + public static Path.Thumbnail thumbnail(Path.Command command, int size, boolean disableOpt) { return Path.Thumbnail.newBuilder() .setCommand(command) @@ -350,6 +401,34 @@ public static Path.Thumbnail thumbnail(Path.ResourceData resource, int size, boo .build(); } + public static Path.Thumbnail thumbnail(CommandIndex command, int attachment, int size, boolean disableOpt) { + return Path.Thumbnail.newBuilder() + .setFramebufferAttachment(framebufferAttachmentAfter(command, attachment, + renderSettings(size, size, Path.DrawMode.NORMAL, disableOpt), + Path.UsageHints.newBuilder() + .setPreview(true) + .build()).getFramebufferAttachment()) + .setDesiredFormat(Images.FMT_RGBA_U8_NORM) + .setDesiredMaxHeight(size) + .setDesiredMaxWidth(size) + .setDisableOptimization(disableOpt) + .build(); + } + + public static Path.Any thumbnails( + CommandIndex command, Path.ResourceType type, int size, boolean disableOpt) { + return thumbnail(Path.Thumbnail.newBuilder() + .setResources(Path.MultiResourceData.newBuilder() + .setAfter(command.getCommand()) + .setAll(true) + .setType(type)) + .setDesiredFormat(Images.FMT_RGBA_U8_NORM) + .setDesiredMaxHeight(size) + .setDesiredMaxWidth(size) + .setDisableOptimization(disableOpt) + .build()); + } + public static Path.Any thumbnail(Path.Thumbnail thumb) { return Path.Any.newBuilder() .setThumbnail(thumb) @@ -394,6 +473,10 @@ public static boolean isNull(Path.Command c) { return (c == null) || (c.getIndicesCount() == 0); } + public static boolean isNull(Path.Commands c) { + return (c == null) || (c.getFromCount() == 0); + } + /** * Compares a and b, returning -1 if a comes before b, 1 if b comes before a and 0 if they * are equal. @@ -404,17 +487,20 @@ public static int compare(Path.Command a, Path.Command b) { } else if (isNull(b)) { return 1; } + return compareCommands(a.getIndicesList(), b.getIndicesList(), false); + } - for (int i = 0; i < a.getIndicesCount(); i++) { - if (i >= b.getIndicesCount()) { + public static int compareCommands(List a, List b, boolean open) { + for (int i = 0; i < a.size(); i++) { + if (i >= b.size()) { return 1; } - int r = Long.compare(a.getIndices(i), b.getIndices(i)); + int r = Long.compare(a.get(i), b.get(i)); if (r != 0) { return r; } } - return (a.getIndicesCount() == b.getIndicesCount()) ? 0 : -1; + return open || (a.size() == b.size()) ? 0 : -1; } public static boolean contains(Path.Any path, Predicate predicate) { @@ -494,6 +580,64 @@ public static Path.Any reparent(Path.Any path, Path.GlobalState state) { return toAny(head); } + public static class CommandFilter { + public boolean showHostCommands; + public boolean showSubmitInfoNodes; + public boolean showSyncCommands; + public boolean showBeginEndCommands; + + public CommandFilter(boolean showHostCommands, boolean showSubmitInfoNodes, + boolean showSyncCommands, boolean showBeginEndCommands) { + this.showHostCommands = showHostCommands; + this.showSubmitInfoNodes = showSubmitInfoNodes; + this.showSyncCommands = showSyncCommands; + this.showBeginEndCommands = showBeginEndCommands; + } + + public static CommandFilter fromSettings(Settings settings) { + SettingsProto.UI.CommandFilter filter = settings.ui().getCommandFilter(); + return new CommandFilter(filter.getShowHostCommands(), filter.getShowSubmitInfoNodes(), + filter.getShowSyncCommands(), filter.getShowBeginEndCommands()); + } + + public CommandFilter copy() { + return new CommandFilter( + showHostCommands, showSubmitInfoNodes, showSyncCommands, showBeginEndCommands); + } + + public boolean update(CommandFilter from) { + boolean changed = + showHostCommands != from.showHostCommands || + showSubmitInfoNodes != from.showSubmitInfoNodes || + showSyncCommands != from.showSyncCommands || + showBeginEndCommands != from.showBeginEndCommands; + if (changed) { + showHostCommands = from.showHostCommands; + showSubmitInfoNodes = from.showSubmitInfoNodes; + showSyncCommands = from.showSyncCommands; + showBeginEndCommands = from.showBeginEndCommands; + } + return changed; + } + + public Path.CommandTree.Builder toProto(Path.CommandTree.Builder path) { + return path + .setFilter(Path.CommandFilter.newBuilder() + .setSuppressHostCommands(!showHostCommands) + .setSuppressDeviceSideSyncCommands(!showSyncCommands) + .setSuppressBeginEndMarkers(!showBeginEndCommands)) + .setSuppressSubmitInfoNodes(!showSubmitInfoNodes); + } + + public void save(Settings settings) { + SettingsProto.UI.CommandFilter.Builder filter = settings.writeUi().getCommandFilterBuilder(); + filter.setShowHostCommands(showHostCommands); + filter.setShowSubmitInfoNodes(showSubmitInfoNodes); + filter.setShowSyncCommands(showSyncCommands); + filter.setShowBeginEndCommands(showBeginEndCommands); + } + } + /** * Visitor is the interface implemented by types that operate each of the path types. * @param the type of the result value. @@ -512,11 +656,8 @@ private interface Visitor { R visit(Path.CommandTree path, A arg); R visit(Path.CommandTreeNode path, A arg); R visit(Path.CommandTreeNodeForCommand path, A arg); - R visit(Path.Context path, A arg); - R visit(Path.Contexts path, A arg); R visit(Path.Device path, A arg); R visit(Path.DeviceTraceConfiguration path, A arg); - R visit(Path.Events path, A arg); R visit(Path.FramebufferObservation path, A arg); R visit(Path.Field path, A arg); R visit(Path.GlobalState path, A arg); @@ -566,16 +707,10 @@ private static T dispatchAny(Path.Any path, Visitor visitor, A arg) return visitor.visit(path.getCommandTreeNode(), arg); case COMMAND_TREE_NODE_FOR_COMMAND: return visitor.visit(path.getCommandTreeNodeForCommand(), arg); - case CONTEXT: - return visitor.visit(path.getContext(), arg); - case CONTEXTS: - return visitor.visit(path.getContexts(), arg); case DEVICE: return visitor.visit(path.getDevice(), arg); case TRACECONFIG: return visitor.visit(path.getTraceConfig(), arg); - case EVENTS: - return visitor.visit(path.getEvents(), arg); case FBO: return visitor.visit(path.getFBO(), arg); case FIELD: @@ -648,16 +783,10 @@ protected static T dispatch(Object path, Visitor visitor, A arg) { return visitor.visit((Path.CommandTreeNode)path, arg); } else if (path instanceof Path.CommandTreeNodeForCommand) { return visitor.visit((Path.CommandTreeNodeForCommand)path, arg); - } else if (path instanceof Path.Context) { - return visitor.visit((Path.Context)path, arg); - } else if (path instanceof Path.Contexts) { - return visitor.visit((Path.Contexts)path, arg); } else if (path instanceof Path.Device) { return visitor.visit((Path.Device)path, arg); } else if (path instanceof Path.DeviceTraceConfiguration) { return visitor.visit((Path.DeviceTraceConfiguration)path, arg); - } else if (path instanceof Path.Events) { - return visitor.visit((Path.Events)path, arg); } else if (path instanceof Path.FramebufferObservation) { return visitor.visit((Path.FramebufferObservation)path, arg); } else if (path instanceof Path.Field) { @@ -719,11 +848,8 @@ protected static T dispatch(Object path, Visitor visitor, A arg) { @Override public Object visit(Path.CommandTree path, Void ignored) { return path; } @Override public Object visit(Path.CommandTreeNode path, Void ignored) { return path; } @Override public Object visit(Path.CommandTreeNodeForCommand path, Void ignored) { return path; } - @Override public Object visit(Path.Context path, Void ignored) { return path; } - @Override public Object visit(Path.Contexts path, Void ignored) { return path; } @Override public Object visit(Path.Device path, Void ignored) { return path; } @Override public Object visit(Path.DeviceTraceConfiguration path, Void ignored) { return path; } - @Override public Object visit(Path.Events path, Void ignored) { return path; } @Override public Object visit(Path.FramebufferObservation path, Void ignored) { return path; } @Override public Object visit(Path.Field path, Void ignored) { return path; } @Override public Object visit(Path.GlobalState path, Void ignored) { return path; } @@ -810,16 +936,6 @@ public Path.Any visit(Path.CommandTreeNodeForCommand path, Void ignored) { return Path.Any.newBuilder().setCommandTreeNodeForCommand(path).build(); } - @Override - public Path.Any visit(Path.Context path, Void ignored) { - return Path.Any.newBuilder().setContext(path).build(); - } - - @Override - public Path.Any visit(Path.Contexts path, Void ignored) { - return Path.Any.newBuilder().setContexts(path).build(); - } - @Override public Path.Any visit(Path.Device path, Void ignored) { return Path.Any.newBuilder().setDevice(path).build(); @@ -830,11 +946,6 @@ public Path.Any visit(Path.DeviceTraceConfiguration path, Void ignored) { return Path.Any.newBuilder().setTraceConfig(path).build(); } - @Override - public Path.Any visit(Path.Events path, Void ignored) { - return Path.Any.newBuilder().setEvents(path).build(); - } - @Override public Path.Any visit(Path.FramebufferObservation path, Void ignored) { return Path.Any.newBuilder().setFBO(path).build(); @@ -1024,16 +1135,6 @@ public Object visit(Path.CommandTreeNodeForCommand path, Void ignored) { return path.getCommand(); } - @Override - public Object visit(Path.Context path, Void ignored) { - return path.getCapture(); - } - - @Override - public Object visit(Path.Contexts path, Void ignored) { - return path.getCapture(); - } - @Override public Object visit(Path.Device path, Void ignored) { return null; @@ -1044,11 +1145,6 @@ public Object visit(Path.DeviceTraceConfiguration path, Void ignored) { return path.getDevice(); } - @Override - public Object visit(Path.Events path, Void ignored) { - return path.getCapture(); - } - @Override public Object visit(Path.FramebufferObservation path, Void ignored) { return path.getCommand(); @@ -1320,38 +1416,11 @@ public Object visit(Path.CommandTreeNodeForCommand path, Object parent) { } } - @Override - public Object visit(Path.Context path, Object parent) { - if (parent instanceof Path.Capture) { - return path.toBuilder().setCapture((Path.Capture) parent).build(); - } else { - throw new RuntimeException("Path.Context cannot set parent to " + parent.getClass().getName()); - } - } - - @Override - public Object visit(Path.Contexts path, Object parent) { - if (parent instanceof Path.Capture) { - return path.toBuilder().setCapture((Path.Capture) parent).build(); - } else { - throw new RuntimeException("Path.Contexts cannot set parent to " + parent.getClass().getName()); - } - } - @Override public Object visit(Path.Device path, Object parent) { throw new RuntimeException("Path.Device has no parent to set"); } - @Override - public Object visit(Path.Events path, Object parent) { - if (parent instanceof Path.Capture) { - return path.toBuilder().setCapture((Path.Capture) parent).build(); - } else { - throw new RuntimeException("Path.Events cannot set parent to " + parent.getClass().getName()); - } - } - @Override public Object visit(Path.FramebufferObservation path, Object parent) { if (parent instanceof Path.Command) { @@ -1630,10 +1699,6 @@ public StringBuilder visit(Path.CommandTree path, StringBuilder sb) { visit(path.getCapture(), sb) .append(".tree"); append(sb, path.getFilter()).append('['); - if (path.getGroupByApi()) sb.append('A'); - if (path.getGroupByThread()) sb.append('T'); - if (path.getGroupByContext()) sb.append('C'); - if (path.getIncludeNoContextGroups()) sb.append('n'); if (path.getGroupByFrame()) sb.append('F'); if (path.getAllowIncompleteFrame()) sb.append('i'); if (path.getGroupByDrawCall()) sb.append('D'); @@ -1666,20 +1731,6 @@ public StringBuilder visit(Path.CommandTreeNodeForCommand path, StringBuilder sb return sb; } - @Override - public StringBuilder visit(Path.Context path, StringBuilder sb) { - visit(path.getCapture(), sb); - sb.append(".context["); - visit(path.getID(), sb); - sb.append("]"); - return sb; - } - - @Override - public StringBuilder visit(Path.Contexts path, StringBuilder sb) { - return visit(path.getCapture(), sb).append(".contexts"); - } - @Override public StringBuilder visit(Path.Device path, StringBuilder sb) { sb.append("device{"); @@ -1696,21 +1747,6 @@ public StringBuilder visit(Path.DeviceTraceConfiguration path, StringBuilder sb) return sb; } - @Override - public StringBuilder visit(Path.Events path, StringBuilder sb) { - visit(path.getCapture(), sb).append(".events"); - append(sb, path.getFilter()).append('['); - if (path.getFirstInFrame()) sb.append("Fs"); - if (path.getLastInFrame()) sb.append("Fe"); - if (path.getClears()) sb.append("C"); - if (path.getDrawCalls()) sb.append("D"); - if (path.getUserMarkers()) sb.append("M"); - if (path.getPushUserMarkers()) sb.append("Ms"); - if (path.getPopUserMarkers()) sb.append("Me"); - if (path.getFramebufferObservations()) sb.append("O"); - return sb.append(']'); - } - @Override public StringBuilder visit(Path.FramebufferObservation path, StringBuilder sb) { return visit(path.getCommand(), sb).append(".fbo"); @@ -1783,7 +1819,7 @@ public StringBuilder visit(Path.Report path, StringBuilder sb) { visit(path.getDevice(), sb); sb.append(']'); } - return append(sb, path.getFilter()); + return sb; } @Override @@ -1870,12 +1906,6 @@ public StringBuilder visit(Path.Thumbnail path, StringBuilder sb) { private StringBuilder append(StringBuilder sb, Path.CommandFilter filter) { String sep = "(", end = ""; - if (filter.hasContext()) { - sb.append(sep).append("context="); - visit(filter.getContext(), sb); - sep = ","; - end = ")"; - } if (filter.getThreadsCount() > 0) { sb.append(sep).append("threads=").append(filter.getThreadsList()); sep = ","; diff --git a/gapic/src/main/com/google/gapid/util/Pods.java b/gapic/src/main/com/google/gapid/util/Pods.java index bfd28e600d..31d81470e4 100644 --- a/gapic/src/main/com/google/gapid/util/Pods.java +++ b/gapic/src/main/com/google/gapid/util/Pods.java @@ -63,6 +63,11 @@ public static Object unpod(Pod.Value o) { public static StringBuilder append(StringBuilder sb, Pod.Value v) { switch (v.getValCase()) { case VAL_NOT_SET: return sb.append("[null]"); + case CHAR: { + int charValue = v.getChar(); + if (charValue == 0) return sb.append("\\0"); + return sb.append((char)charValue); + } case STRING: return sb.append(v.getString()); case BOOL: return sb.append(v.getBool()); case FLOAT64: return sb.append(v.getFloat64()); diff --git a/gapic/src/main/com/google/gapid/util/Ranges.java b/gapic/src/main/com/google/gapid/util/Ranges.java index d6a6c0a234..44a08c7cb0 100644 --- a/gapic/src/main/com/google/gapid/util/Ranges.java +++ b/gapic/src/main/com/google/gapid/util/Ranges.java @@ -17,6 +17,7 @@ import com.google.common.primitives.UnsignedLongs; import com.google.gapid.proto.service.Service; +import com.google.gapid.proto.service.path.Path; import java.util.ArrayList; import java.util.List; @@ -101,4 +102,64 @@ public static List merge(List ranges) throw new UnsupportedOperationException(); }); } + + public static boolean contains(Path.Commands range, Path.Command command) { + return Paths.compareCommands(range.getFromList(), command.getIndicesList(), false) <= 0 && + Paths.compareCommands(range.getToList(), command.getIndicesList(), true) >= 0; + } + + public static boolean containsStart(Path.Commands range, Path.Commands command) { + return Paths.compareCommands(range.getFromList(), command.getFromList(), false) <= 0 && + Paths.compareCommands(range.getToList(), command.getFromList(), true) >= 0; + } + + + /** + * Returns how the given command relates to the given range: + * -1 if it comes before, 0 if it's within, and 1 if it's after the range. + */ + public static int compare(Path.Commands range, Path.Command command) { + int r = Paths.compareCommands(command.getIndicesList(), range.getFromList(), false); + if (r > 0) { // Only need to compare to the end if command is after start. + // If command is before or equal end, it's within the range, otherwise after. + if (Paths.compareCommands(range.getToList(), command.getIndicesList(), true) >= 0) { + r = 0; + } + } + return r; + } + + /** + * Same as above compare with command being the second range's start. + */ + public static int compareStart(Path.Commands range, Path.Commands command) { + int r = Paths.compareCommands(command.getFromList(), range.getFromList(), false); + if (r > 0) { // Only need to compare to the end if b.From is after start. + // If b.From is before or equal end, it's within the range, otherwise after. + if (Paths.compareCommands(range.getToList(), command.getFromList(), true) >= 0) { + r = 0; + } + } + return r; + } + + /** + * Compares a and b, returning + * -1 if a.start < b.start or if a.start == b.start and a.end < b.end, + * 1 if a.start > b.start or if a.start == b.start and a.end > b.end, + * 0 if they are equal. + */ + public static int compare(Path.Commands a, Path.Commands b) { + if (Paths.isNull(a)) { + return Paths.isNull(b) ? 0 : -1; + } else if (Paths.isNull(b)) { + return 1; + } + int r = Paths.compareCommands(a.getFromList(), b.getFromList(), false); + if (r == 0) { + r = Paths.compareCommands(a.getToList(), b.getToList(), false); + } + return r; + } + } diff --git a/gapic/src/main/com/google/gapid/util/StatusWatcher.java b/gapic/src/main/com/google/gapid/util/StatusWatcher.java index 404d2665b5..a4a5faece4 100644 --- a/gapic/src/main/com/google/gapid/util/StatusWatcher.java +++ b/gapic/src/main/com/google/gapid/util/StatusWatcher.java @@ -231,8 +231,12 @@ public synchronized String getSummary() { } if (executing > 0) { sb.append(sep).append(executing).append(" Running"); - if (totalInstr > 0) { + if (totalInstr > 0 && doneInstr > 0) { sb.append(" ").append((int)(((double)doneInstr / totalInstr) * 100)).append("%"); + } else { + // TODO(pmuetschard): This assumes that 0 done means state reconstruction. See server side + // for more details. + sb.append(" - Initializing"); } } return (sb.length() == 0) ? "Idle" : sb.toString(); diff --git a/gapic/src/main/com/google/gapid/util/Streams.java b/gapic/src/main/com/google/gapid/util/Streams.java index ba3404bbae..cd0a0239df 100644 --- a/gapic/src/main/com/google/gapid/util/Streams.java +++ b/gapic/src/main/com/google/gapid/util/Streams.java @@ -34,9 +34,9 @@ public class Streams { // F16 represents a 16-bit signed, floating-point number. public static final Stream.DataType F16 = newFloat(true, 5, 10); // F32 represents a 32-bit signed, floating-point number. - public static final Stream.DataType F32 = newFloat(true, 7, 24); + public static final Stream.DataType F32 = newFloat(true, 8, 23); // F64 represents a 64-bit signed, floating-point number. - public static final Stream.DataType F64 = newFloat(true, 10, 53); + public static final Stream.DataType F64 = newFloat(true, 11, 52); // LINEAR is a Sampling state using a linear curve. public static final Stream.Sampling LINEAR = Stream.Sampling.newBuilder() diff --git a/gapic/src/main/com/google/gapid/util/Strings.java b/gapic/src/main/com/google/gapid/util/Strings.java index 9f8c79c9ec..de38c8ec61 100644 --- a/gapic/src/main/com/google/gapid/util/Strings.java +++ b/gapic/src/main/com/google/gapid/util/Strings.java @@ -15,6 +15,8 @@ */ package com.google.gapid.util; +import com.google.gapid.proto.service.path.Path; + /** * String utilities. */ @@ -26,4 +28,8 @@ public static String stripQuotes(String s) { return (s == null || s.length() < 2 || s.charAt(0) != '"' || s.charAt(s.length() - 1) != '"') ? s : s.substring(1, s.length() - 1); } + + public static String toString(Path.ID id) { + return new String(ProtoDebugTextFormat.escapeBytes(id.getData())); + } } diff --git a/gapic/src/main/com/google/gapid/util/Trees.java b/gapic/src/main/com/google/gapid/util/Trees.java index 753363b0bb..d9be27794a 100644 --- a/gapic/src/main/com/google/gapid/util/Trees.java +++ b/gapic/src/main/com/google/gapid/util/Trees.java @@ -20,6 +20,7 @@ import com.google.common.collect.Sets; +import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; @@ -32,11 +33,49 @@ public class Trees { private Trees() { } + public static TreeItem getTopItem(Tree tree) { + if (!OS.isLinux) { + return tree.getTopItem(); + } + + // Dirty work around for https://bugs.eclipse.org/bugs/show_bug.cgi?id=563232. + // Note this will often return an item that is above the visible one, as it only considers + // root items. The actual correct top item may be a child of the returned item. + int count = tree.getItemCount(); + if (count == 0) { + return null; + } + + TreeItem item = tree.getItem(0); + if (count <= 2) { + return item; + } + Rectangle bounds = item.getBounds(); + if (bounds.y >= 0 || (bounds.y <= 0 && bounds.y + bounds.height >= 0)) { + return item; + } + + int start = 0, end = count - 1; + while (end - start > 1) { + int mid = (start + end) / 2; + item = tree.getItem(mid); + bounds = item.getBounds(); + if (bounds.y <= 0 && bounds.y + bounds.height >= 0) { + return item; + } else if (bounds.y > 0) { + end = mid; + } else { + start = mid; + } + } + return tree.getItem(start); + } + /** * @return the {@link TreeItem TreeItems} that are currently visible in the tree. */ public static Set getVisibleItems(Tree tree) { - TreeItem top = tree.getTopItem(); + TreeItem top = getTopItem(tree); if (top == null) { // Work around bug where getTopItem() returns null when scrolling // up past the top item (elastic scroll). diff --git a/gapic/src/main/com/google/gapid/util/URLs.java b/gapic/src/main/com/google/gapid/util/URLs.java index 52d73b9eff..7f5f864d0e 100644 --- a/gapic/src/main/com/google/gapid/util/URLs.java +++ b/gapic/src/main/com/google/gapid/util/URLs.java @@ -15,7 +15,47 @@ */ package com.google.gapid.util; -public interface URLs { +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class URLs { public static final String FILE_BUG_URL = - "https://github.com/google/gapid/issues/new?template=standard-bug-report-for-gapid.md"; -} \ No newline at end of file + "https://github.com/google/agi/issues/new?template=standard-bug-report-for-gapid.md"; + public static final String DEVICE_COMPATIBILITY_URL = "https://developer.android.com/agi/supported-devices"; + public static final String EXPECTED_ANGLE_PREFIX = "https://agi-angle.storage.googleapis.com/"; + public static final String ANGLE_DOWNLOAD = EXPECTED_ANGLE_PREFIX + "index.html"; + + private URLs() { + } + + public static boolean downloadWithProgressUpdates( + URL url, OutputStream out, DownloadProgressListener listener) throws IOException { + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + long size = con.getContentLength(); + listener.onProgress(0, size); + + try (BufferedInputStream in = new BufferedInputStream(con.getInputStream())) { + byte[] buffer = new byte[4096]; + long done = 0; + int now; + while ((now = in.read(buffer, 0, buffer.length)) >= 0) { + done += now; + out.write(buffer, 0, now); + if (!listener.onProgress(done, size)) { + return false; + } + } + } + return true; + } + + public static interface DownloadProgressListener { + @SuppressWarnings("unused") + public default boolean onProgress(long done, long total) { + return true; + } + } +} diff --git a/gapic/src/main/com/google/gapid/util/UpdateWatcher.java b/gapic/src/main/com/google/gapid/util/UpdateWatcher.java index d149c09107..8686cb4b50 100644 --- a/gapic/src/main/com/google/gapid/util/UpdateWatcher.java +++ b/gapic/src/main/com/google/gapid/util/UpdateWatcher.java @@ -15,16 +15,28 @@ */ package com.google.gapid.util; -import static com.google.gapid.util.MoreFutures.logFailure; +import static com.google.gapid.util.MoreFutures.logFailureIgnoringCancel; +import static com.google.gapid.util.Scheduler.EXECUTOR; +import static com.google.gapid.views.ErrorDialog.showErrorDialog; +import static com.google.gapid.widgets.Widgets.scheduleIfNotDisposed; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.eclipse.jface.dialogs.MessageDialog.openInformation; import com.google.common.util.concurrent.ListenableFuture; +import com.google.gapid.models.Models; import com.google.gapid.models.Settings; import com.google.gapid.proto.SettingsProto; -import com.google.gapid.proto.service.Service.Release; +import com.google.gapid.proto.service.Service; import com.google.gapid.server.Client; +import com.google.gapid.views.StatusBar; + +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.program.Program; +import org.eclipse.swt.widgets.Shell; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Logger; /** @@ -37,49 +49,114 @@ public class UpdateWatcher { private final Settings settings; private final Client client; - private final Listener listener; - - /** Callback interface */ - public interface Listener { - /** Called whenever a new release is found. */ - void onNewReleaseAvailable(Release release); - } + private final StatusBar statusBar; + private final AtomicBoolean scheduled = new AtomicBoolean(false); + private ListenableFuture scheduledCheck; - public UpdateWatcher(Settings settings, Client client, Listener listener) { + public UpdateWatcher(Settings settings, Client client, StatusBar statusBar) { this.settings = settings; this.client = client; - this.listener = listener; - if (settings.preferences().getUpdateAvailable()) { - logFailure(LOG, Scheduler.EXECUTOR.schedule(this::doCheck, 0, TimeUnit.MILLISECONDS)); - } else { - scheduleCheck(); + this.statusBar = statusBar; + } + + public void watchForUpdates() { + if (!scheduled.getAndSet(true)) { + scheduleCheck(Listener.NULL_LISTENER, settings.preferences().getUpdateAvailable()); } } - private void scheduleCheck() { - long now = System.currentTimeMillis(); - long timeSinceLastUpdateMS = now - settings.preferences().getLastCheckForUpdates(); - long delay = Math.max(CHECK_INTERVAL_MS - timeSinceLastUpdateMS, 0); - logFailure(LOG, Scheduler.EXECUTOR.schedule(this::doCheck, delay, TimeUnit.MILLISECONDS)); + public void checkNow(Listener listener) { + if (scheduled.getAndSet(true)) { + scheduledCheck.cancel(false); + } + scheduleCheck(listener, true); } - private void doCheck() { + public static void manualUpdateCheck(Shell shell, Models models) { + models.updateWatcher.checkNow(new Listener() { + @Override + public boolean forceCheck() { + return true; + } + + @Override + public void onCompleted(Service.Releases releases) { + scheduleIfNotDisposed(shell, () -> { + if (GapidVersion.GAPID_VERSION.isOlderThan(releases.getAGI())) { + MessageDialog dialog = new MessageDialog(shell, Messages.UPDATE_CHECK_TITLE, + null, Messages.UPDATE_CHECK_UPDATE, MessageDialog.INFORMATION, + new String[] { "Download", "Ignore" }, 0); + if (dialog.open() == 0) { + // Download was clicked. + Program.launch(releases.getAGI().getBrowserUrl()); + } + } else { + openInformation(shell, Messages.UPDATE_CHECK_TITLE, Messages.UPDATE_CHECK_NO_UPDATE); + } + }); + } + + @Override + public void onFailure(Throwable t) { + scheduleIfNotDisposed(shell, () -> + showErrorDialog(shell, models.analytics, "Failed to check for updates", t)); + } + }); + } + + private void scheduleCheck(Listener listener, boolean immediate) { + long delay = 0; + if (!immediate) { + long now = System.currentTimeMillis(); + long timeSinceLastUpdateMS = now - settings.preferences().getLastCheckForUpdates(); + delay = Math.max(CHECK_INTERVAL_MS - timeSinceLastUpdateMS, 0); + } + scheduledCheck = logFailureIgnoringCancel( + LOG, EXECUTOR.schedule(() -> doCheck(listener), delay, MILLISECONDS)); + } + + private void doCheck(Listener listener) { SettingsProto.Preferences.Builder prefs = settings.writePreferences(); - if (prefs.getCheckForUpdates()) { - ListenableFuture future = client.checkForUpdates(prefs.getIncludeDevReleases()); + if (listener.forceCheck() || prefs.getCheckForUpdates()) { + ListenableFuture future = + client.checkForUpdates(prefs.getIncludeDevReleases()); prefs.setUpdateAvailable(false); try { - Release release = future.get(); - if (release != null) { + Service.Releases releases = future.get(); + if (GapidVersion.GAPID_VERSION.isOlderThan(releases.getAGI())) { prefs.setUpdateAvailable(true); - listener.onNewReleaseAvailable(release); + onNewReleaseAvailable(releases.getAGI()); } - } catch (InterruptedException | ExecutionException e) { + prefs.setLatestAngleRelease(releases.getANGLE()); + listener.onCompleted(releases); + } catch (InterruptedException e) { /* never mind */ + } catch (ExecutionException e) { + listener.onFailure(e.getCause()); } } prefs.setLastCheckForUpdates(System.currentTimeMillis()); settings.save(); - scheduleCheck(); + scheduleCheck(Listener.NULL_LISTENER, false); + } + + private void onNewReleaseAvailable(Service.Releases.AGIRelease release) { + scheduleIfNotDisposed(statusBar, () -> { + statusBar.setNotification("New update available", () -> { + Program.launch(release.getBrowserUrl()); + }); + }); + } + + /** + * Callback interface for manual update checks. + */ + @SuppressWarnings("unused") + public static interface Listener { + public static final Listener NULL_LISTENER = new Listener() { /* empty */ }; + + public default boolean forceCheck() { return false; } + public default void onCompleted(Service.Releases releases) { /* do nothing */ } + public default void onFailure(Throwable t) { /* do nothing */ } } } diff --git a/gapic/src/main/com/google/gapid/util/Version.java b/gapic/src/main/com/google/gapid/util/Version.java index 227aa32052..e80038bc21 100644 --- a/gapic/src/main/com/google/gapid/util/Version.java +++ b/gapic/src/main/com/google/gapid/util/Version.java @@ -24,22 +24,60 @@ public class Version { public final int minor; public final int point; public final String build; + public final int year; - public Version(int major, int minor, int point, String build) { + public Version(int major, int minor, int point, String build, int year) { this.major = major; this.minor = minor; this.point = point; this.build = build; + this.year = year; } public static Version fromProto(Service.ServerInfo info) { - return new Version(info.getVersionMajor(), info.getVersionMinor(), info.getVersionPoint(), ""); + return new Version(info.getVersionMajor(), info.getVersionMinor(), info.getVersionPoint(), "", 0); + } + + public int getDevVersion() { + // For the dev builds, the build has format dev-YYYYMMDD. + if (build.startsWith("dev-") && build.length() >= 12) { + try { + return Integer.parseInt(build.substring(4, 12)); + } catch (NumberFormatException e) { + // Ignore. + } + } + return -1; + } + + public boolean isDeveloper() { + return "developer".equals(build); } public boolean isCompatible(Version version) { return major == version.major && minor == version.minor; } + public boolean isOlderThan(Service.Releases.AGIRelease release) { + if (isDeveloper()) { + return false; + } else if (major < release.getVersionMajor()) { + return true; + } else if (major > release.getVersionMajor()) { + return false; + } else if (minor < release.getVersionMinor()) { + return true; + } else if (minor > release.getVersionMinor()) { + return false; + } else if (point < release.getVersionPoint()) { + return true; + } else if (point > release.getVersionPoint()) { + return false; + } + int devVersion = getDevVersion(); + return devVersion >= 0 && devVersion < release.getVersionDev(); + } + @Override public int hashCode() { return major << 22 | minor << 12 | point; @@ -65,6 +103,11 @@ public String toFriendlyString() { return major + "." + minor + "." + point; } + public String toStringWithYear(boolean includeBuild) { + return (year == 0 ? "" : year + "-") + major + "." + minor + "." + point + + (!includeBuild || build.isEmpty() ? "" : ":" + build); + } + public String toPatternString() { return major + "." + minor + ".*"; } diff --git a/gapic/src/main/com/google/gapid/views/AboutDialog.java b/gapic/src/main/com/google/gapid/views/AboutDialog.java index 897a761fb4..3367a69a31 100644 --- a/gapic/src/main/com/google/gapid/views/AboutDialog.java +++ b/gapic/src/main/com/google/gapid/views/AboutDialog.java @@ -18,7 +18,7 @@ import static com.google.gapid.util.GapidVersion.GAPID_VERSION; import static com.google.gapid.widgets.Widgets.createComposite; import static com.google.gapid.widgets.Widgets.createLabel; -import static com.google.gapid.widgets.Widgets.createTextbox; +import static com.google.gapid.widgets.Widgets.createSelectableLabel; import static com.google.gapid.widgets.Widgets.withMargin; import static java.util.logging.Level.SEVERE; @@ -34,6 +34,7 @@ import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.program.Program; @@ -42,7 +43,6 @@ import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; import java.io.IOException; import java.util.logging.Logger; @@ -51,7 +51,7 @@ * Dialog showing some basic info about our application. */ public class AboutDialog { - private static final String HELP_URL = "https://google.github.io/gapid"; + private static final String HELP_URL = "https://gpuinspector.dev"; private static final Logger LOG = Logger.getLogger(AboutDialog.class.getName()); private AboutDialog() { @@ -89,23 +89,23 @@ protected Control createDialogArea(Composite parent) { Label logo = createLabel(container, "", theme.dialogLogo()); logo.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, false, 2, 1)); - Text title = createForegroundLabel(container, Messages.WINDOW_TITLE); + StyledText title = createSelectableLabel(container, Messages.WINDOW_TITLE); title.setFont(theme.bigBoldFont()); title.setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, false, 2, 1)); createLabel(container, "").setLayoutData(new GridData(SWT.CENTER, SWT.FILL, true, true, 2, 1)); Button clipboard = Widgets.createButton(container, "", e -> { - String textData = "Version " + GAPID_VERSION; + String textData = "Version " + GAPID_VERSION.toStringWithYear(true); widgets.copypaste.setContents(textData); }); clipboard.setImage(theme.clipboard()); clipboard.setLayoutData(new GridData(SWT.CENTER, SWT.BEGINNING, true, true, 1, 3)); - createForegroundLabel(container, "Version " + GAPID_VERSION); - createForegroundLabel( + createSelectableLabel(container, "Version " + GAPID_VERSION.toStringWithYear(true)); + createSelectableLabel( container, "Server: " + Info.getServerName() + ", Version: " + Info.getServerVersion()); - createForegroundLabel(container, Messages.ABOUT_COPY); + createSelectableLabel(container, Messages.ABOUT_COPY); return area; } @@ -114,15 +114,6 @@ protected Control createDialogArea(Composite parent) { protected void createButtonsForButtonBar(Composite parent) { createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); } - - public Text createForegroundLabel(Composite parent, String text) { - Text label = createTextbox(parent, SWT.READ_ONLY, text); - label.setBackground(parent.getBackground()); - - // SWT will weirdly select the entire content of the first textbox. No thanks. - label.setSelection(0, 0); - return label; - } }.open(); } } diff --git a/gapic/src/main/com/google/gapid/views/ActivityPickerDialog.java b/gapic/src/main/com/google/gapid/views/ActivityPickerDialog.java index 878fdc3fd0..2326272b9a 100644 --- a/gapic/src/main/com/google/gapid/views/ActivityPickerDialog.java +++ b/gapic/src/main/com/google/gapid/views/ActivityPickerDialog.java @@ -202,7 +202,7 @@ protected Control createDialogArea(Composite parent) { Composite container = createComposite(area, new GridLayout(1, false)); container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - SearchBox search = new SearchBox(container, true); + SearchBox search = new SearchBox(container, "Filter activities...", true); search.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); loading = LoadablePanel.create(container, widgets, p -> createTreeForViewer(p, SWT.BORDER)); diff --git a/gapic/src/main/com/google/gapid/views/CommandOptions.java b/gapic/src/main/com/google/gapid/views/CommandOptions.java new file mode 100644 index 0000000000..64f91511e7 --- /dev/null +++ b/gapic/src/main/com/google/gapid/views/CommandOptions.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 Google Inc. + * + * 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. + */ +package com.google.gapid.views; + +import static com.google.gapid.util.Paths.lastCommand; + +import com.google.gapid.models.CommandStream; +import com.google.gapid.models.Models; +import com.google.gapid.proto.service.path.Path; +import com.google.gapid.util.Experimental; +import com.google.gapid.widgets.Widgets; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Menu; +import org.eclipse.swt.widgets.MenuItem; + +import java.util.List; +import java.util.stream.Collectors; + +public class CommandOptions { + private CommandOptions() { + } + + public static void CreateCommandOptionsMenu(Control parent, Widgets widgets, Models models) { + final Menu optionsMenu = new Menu(parent); + + /* TODO(pmuetschard): re-enable the command-tree popup menu. + MenuItem editMenuItem = Widgets.createMenuItem(optionsMenu , "&Edit", SWT.MOD1 + 'E', e -> { + CommandStream.Node node = tree.getSelection(); + if (node != null && node.getData() != null && node.getCommand() != null) { + widgets.editor.showEditPopup(optionsMenu.getShell(), lastCommand(node.getData().getCommands()), + node.getCommand(), node.device); + } + }); + + MenuItem disableMenuItem; + MenuItem enableMenuItem; + MenuItem isolateMenuItem; + if (Experimental.enableProfileExperiments(models.settings)) { + disableMenuItem = Widgets.createMenuItem(optionsMenu, "Disable Drawcall", SWT.MOD1 + 'D', e -> { + CommandStream.Node node = tree.getSelection(); + if (node != null && node.getData() != null) { + widgets.experiments.disableCommands(node.getData().getExperimentalCommandsList()); + } + tree.updateTree(tree.getSelectionItem()); + }); + + enableMenuItem = Widgets.createMenuItem(optionsMenu, "Enable Drawcall", SWT.MOD1 + 'E', e -> { + CommandStream.Node node = tree.getSelection(); + if (node != null && node.getData() != null) { + widgets.experiments.enableCommands(node.getData().getExperimentalCommandsList()); + } + tree.updateTree(tree.getSelectionItem()); + }); + + isolateMenuItem = Widgets.createMenuItem(optionsMenu, "Disable Other Drawcalls", SWT.MOD1 + 'I', e -> { + CommandStream.Node node = tree.getSelection(); + if (node != null && node.getData() != null) { + widgets.experiments.disableCommands(getSiblings(node)); + } + tree.updateTree(tree.getSelectionItem()); + }); + } else { + disableMenuItem = null; + enableMenuItem = null; + isolateMenuItem = null; + } + + tree.setPopupMenu(optionsMenu, node -> { + if (node.getData() == null) { + return false; + } + + editMenuItem.setEnabled(false); + if (node.getCommand() != null && CommandEditor.shouldShowEditPopup(node.getCommand())) { + editMenuItem.setEnabled(true); + } + + boolean canBeDisabled = node.getData().getExperimentalCommandsCount() > 0; + boolean canBeIsolated = node.getParent().getData().getExperimentalCommandsCount() > 1; + + if (disableMenuItem != null) { + boolean disabled = widgets.experiments.areAllCommandsDisabled( + node.getData().getExperimentalCommandsList()); + disableMenuItem.setEnabled(canBeDisabled && !disabled); + } + + if (enableMenuItem != null) { + boolean hasDisabledChildren = widgets.experiments.isAnyCommandDisabled( + node.getData().getExperimentalCommandsList()); + enableMenuItem.setEnabled(canBeDisabled && hasDisabledChildren); + } + + if (isolateMenuItem != null) { + isolateMenuItem.setEnabled(canBeDisabled && canBeIsolated); + } + return true; + }); + */ + } + + private static List getSiblings(CommandStream.Node node) { + List experimentalCommands = node.getData().getExperimentalCommandsList(); + return node.getParent().getData().getExperimentalCommandsList() + .stream() + .filter(cmd -> !experimentalCommands.contains(cmd)) + .collect(Collectors.toList()); + } +} diff --git a/gapic/src/main/com/google/gapid/views/CommandTree.java b/gapic/src/main/com/google/gapid/views/CommandTree.java index e315418717..2c4a84e3ba 100644 --- a/gapic/src/main/com/google/gapid/views/CommandTree.java +++ b/gapic/src/main/com/google/gapid/views/CommandTree.java @@ -15,181 +15,486 @@ */ package com.google.gapid.views; +import static com.google.gapid.image.Images.createNonScaledImage; import static com.google.gapid.image.Images.noAlpha; +import static com.google.gapid.models.Follower.nullPrefetcher; import static com.google.gapid.models.ImagesModel.THUMB_SIZE; -import static com.google.gapid.util.Colors.getRandomColor; -import static com.google.gapid.util.Colors.lerp; -import static com.google.gapid.util.Loadable.MessageType.Error; -import static com.google.gapid.util.Paths.lastCommand; +import static com.google.gapid.models.ImagesModel.scaleImage; +import static com.google.gapid.perfetto.canvas.Tooltip.LocationComputer.horizontallyCenteredAndConstrained; +import static com.google.gapid.perfetto.canvas.Tooltip.LocationComputer.standardTooltip; +import static com.google.gapid.perfetto.canvas.Tooltip.LocationComputer.verticallyCenteredAndConstrained; +import static com.google.gapid.util.Logging.throttleLogRpcError; +import static com.google.gapid.widgets.Widgets.createBaloonToolItem; +import static com.google.gapid.widgets.Widgets.createButton; +import static com.google.gapid.widgets.Widgets.createCheckbox; +import static com.google.gapid.widgets.Widgets.createComposite; +import static com.google.gapid.widgets.Widgets.createLabel; +import static com.google.gapid.widgets.Widgets.createToggleButton; +import static com.google.gapid.widgets.Widgets.createVerticalSash; +import static com.google.gapid.widgets.Widgets.scheduleIfNotDisposed; +import static com.google.gapid.widgets.Widgets.withLayoutData; +import static com.google.gapid.widgets.Widgets.withMarginOnly; +import static java.util.function.Function.identity; +import static java.util.logging.Level.WARNING; +import static java.util.stream.Collectors.toList; import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.gapid.models.Analytics.View; -import com.google.gapid.models.ApiContext; -import com.google.gapid.models.ApiContext.FilteringContext; import com.google.gapid.models.Capture; import com.google.gapid.models.CommandStream; import com.google.gapid.models.CommandStream.CommandIndex; -import com.google.gapid.models.CommandStream.Node; import com.google.gapid.models.Follower; +import com.google.gapid.models.ImagesModel; import com.google.gapid.models.Models; +import com.google.gapid.models.Profile; +import com.google.gapid.perfetto.canvas.Area; +import com.google.gapid.perfetto.canvas.Fonts; +import com.google.gapid.perfetto.canvas.Fonts.Style; +import com.google.gapid.perfetto.canvas.RenderContext; +import com.google.gapid.perfetto.canvas.Size; +import com.google.gapid.perfetto.canvas.Tooltip; +import com.google.gapid.perfetto.views.TraceConfigDialog.GpuCountersDialog; +import com.google.gapid.proto.device.GpuProfiling; import com.google.gapid.proto.service.Service; import com.google.gapid.proto.service.Service.ClientAction; -import com.google.gapid.proto.service.api.API; import com.google.gapid.proto.service.path.Path; import com.google.gapid.rpc.Rpc; +import com.google.gapid.rpc.Rpc.Result; import com.google.gapid.rpc.RpcException; import com.google.gapid.rpc.SingleInFlight; import com.google.gapid.rpc.UiCallback; +import com.google.gapid.server.Client.DataUnavailableException; import com.google.gapid.util.Events; +import com.google.gapid.util.Experimental; import com.google.gapid.util.Loadable; import com.google.gapid.util.Messages; import com.google.gapid.util.MoreFutures; +import com.google.gapid.util.Paths; +import com.google.gapid.util.Scheduler; import com.google.gapid.util.SelectionHandler; +import com.google.gapid.views.Formatter.LinkableStyledString; import com.google.gapid.views.Formatter.StylingString; -import com.google.gapid.widgets.LinkifiedTreeWithImages; -import com.google.gapid.widgets.LoadableImage; -import com.google.gapid.widgets.LoadableImageWidget; -import com.google.gapid.widgets.LoadablePanel; +import com.google.gapid.widgets.LoadingIndicator; import com.google.gapid.widgets.SearchBox; import com.google.gapid.widgets.Widgets; import org.eclipse.jface.viewers.TreePath; +import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.custom.StyleRange; +import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.RGBA; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Menu; -import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Sash; +import org.eclipse.swt.widgets.Slider; +import org.eclipse.swt.widgets.ToolBar; +import org.eclipse.swt.widgets.Widget; -import java.util.Iterator; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.logging.Level; import java.util.logging.Logger; +import java.util.stream.Collectors; -/** - * API command view displaying the commands with their hierarchy grouping in a tree. - */ -public class CommandTree extends Composite - implements Tab, Capture.Listener, CommandStream.Listener, ApiContext.Listener { +public class CommandTree extends Canvas + implements Tab, Capture.Listener, CommandStream.Listener, Profile.Listener { protected static final Logger LOG = Logger.getLogger(CommandTree.class.getName()); + private static final String COMMAND_INDEX_HOVER = "Double click to copy index. Use Ctrl+G to jump to a given command index."; + private static final String COMMAND_INDEX_DSCRP = "Command index: "; + + private static final int SASH_WIDTH = 3; + private static final int MIN_CHILD_SIZE = 150; + private static final double Y_PADDING = 5; + private static final double HEADER_MARGIN = 10; + private static final double COLUMN_SPACING = 8; + private static final double ROW_SPACING = 4; + private static final double TREE_INDENT = 10; + private static final double CARRET_SIZE_LONG = 8; + private static final double CARRET_SIZE_SHORT = 4; + private static final double CARRET_STROKE = 1.75; + private static final double CARRET_Y_PADDING = 4; + private static final double CARRET_X_SIZE = CARRET_SIZE_LONG + 2; + private static final int IMAGE_SIZE = 18; + private static final double IMAGE_PADDING = 6; + private static final int PREVIEW_HOVER_DELAY_MS = 500; + private static final double TOOLTIP_OFFSET = 5; private final Models models; - private final LoadablePanel loading; - protected final Tree tree; + private final Widgets widgets; + private final Paths.CommandFilter filter; + + private final RenderContext.Global context; + private final SizeData size; + private final TreeState tree; + private final ImageProvider images; + + private final MatchingRowsLayout layout; + private final Slider treeHBar; + private final Slider tableHBar; + private final Slider commonVBar; + private final ToggleButtonBar counterGroupButtons; + private final Label commandIdx; private final SelectionHandler selectionHandler; private final SingleInFlight searchController = new SingleInFlight(); + private Loadable.Message tableMessage = Loadable.Message.loading(Messages.LOADING_CAPTURE); + private int hoveredRow = -1; + private TreeState.Row rowMarkedAsHovered = null; + private Follower.Prefetcher lastPrefetcher = nullPrefetcher(); + private int selectedRow = -1; + private Tooltip columnTooltip = null; + private Tooltip valueTooltip = null; + private CommandStream.Node hoveredNode = null; + private Future lastScheduledFuture = Futures.immediateFuture(null); + private boolean showImagePreview = false; + + private final LoadingIndicator.Repaintable loadingRepainter; + private final LoadingIndicator.Repaintable previewRepainter; + public CommandTree(Composite parent, Models models, Widgets widgets) { - super(parent, SWT.NONE); + super(parent, SWT.NO_BACKGROUND | SWT.DOUBLE_BUFFERED); this.models = models; + this.widgets = widgets; + this.filter = models.commands.getFilter(); + + this.context = new RenderContext.Global(widgets.theme, this); + this.size = new SizeData(); + this.tree = new TreeState(); + this.images = new ImageProvider(this, models.images); + + this.layout = new MatchingRowsLayout(size, models.settings.ui().getCommandSplitterRatio()); + setLayout(layout); + + Composite topLeft = createComposite(this, withMarginOnly(new GridLayout(2, false), 0, 0)); + SearchBox search = withLayoutData(new SearchBox(topLeft, "Search commands...", false), + new GridData(SWT.FILL, SWT.CENTER, true, true)); + ToolBar bar = withLayoutData( + new ToolBar(topLeft, SWT.FLAT), new GridData(SWT.RIGHT, SWT.CENTER, false, true)); + createBaloonToolItem(bar, widgets.theme.filter(), bubble -> { + bubble.setLayout(new GridLayout(1, false)); + createCheckbox(bubble, "Show Host Commands", filter.showHostCommands, + e -> filter.showHostCommands = ((Button)e.widget).getSelection()); + createCheckbox(bubble, "Show Submit Info Nodes", filter.showSubmitInfoNodes, + e -> filter.showSubmitInfoNodes = ((Button)e.widget).getSelection()); + createCheckbox(bubble, "Show Event/Sync Commands", filter.showSyncCommands, + e -> filter.showSyncCommands = ((Button)e.widget).getSelection()); + createCheckbox(bubble, "Show Begin/End Commands", filter.showBeginEndCommands, + e -> filter.showBeginEndCommands = ((Button)e.widget).getSelection()); + withLayoutData(createButton(bubble, "Apply", e -> bubble.close()), + new GridData(SWT.RIGHT, SWT.TOP, false, false)); + + bubble.addListener(SWT.Close, e -> models.commands.setFilter(filter)); + }, "Filter"); + + createVerticalSash(this, this::onSashMoved); + + int topRightCount = 1 /* toolbar */; + if (Experimental.enableProfileExperiments(models.settings)) { + topRightCount++; /* experiments button */ + } + Composite topRight = createComposite( + this, withMarginOnly(new GridLayout(topRightCount, false), 0, 0)); - setLayout(new GridLayout(1, false)); + if (Experimental.enableProfileExperiments(models.settings)) { + Button experimentsButton = withLayoutData(createButton(topRight, "Experiments", e -> + widgets.experiments.showExperimentsPopup(getShell())), + new GridData(SWT.LEFT, SWT.CENTER, false, true)); + experimentsButton.setImage(widgets.theme.science()); + } + + counterGroupButtons = withLayoutData(new ToggleButtonBar(topRight), + new GridData(SWT.LEFT, SWT.CENTER, false, true)); - SearchBox search = new SearchBox(this, false); - loading = LoadablePanel.create(this, widgets, p -> new Tree(p, models, widgets)); - tree = loading.getContents(); + treeHBar = new Slider(this, SWT.HORIZONTAL); + tableHBar = new Slider(this, SWT.HORIZONTAL); + commonVBar = new Slider(this, SWT.VERTICAL); - search.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - loading.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite footer = createComposite(this, withMarginOnly(new GridLayout(1, false), 2, 2)); + commandIdx = createLabel(footer, COMMAND_INDEX_DSCRP); + commandIdx.setToolTipText(COMMAND_INDEX_HOVER); + commandIdx.addListener(SWT.MouseDoubleClick, e -> { + if (commandIdx.getText().length() > COMMAND_INDEX_DSCRP.length()) { + widgets.copypaste.setContents(commandIdx.getText().substring(COMMAND_INDEX_DSCRP.length())); + } + }); + + addListener(SWT.Paint, this::onPaint); + addListener(SWT.Resize, e -> layout.onResize(getClientArea().width)); + addListener(SWT.MouseDown, this::onMouseDown); + addListener(SWT.MouseUp, this::onMouseUp); + addListener(SWT.MouseMove, e -> updateHover(e.x, e.y)); + addListener(SWT.MouseExit, e -> updateHover(0, 0)); + addListener(SWT.MouseWheel, this::onMouseWheel); + addListener(SWT.MouseHorizontalWheel, this::onMouseHorizontalWheel); + addListener(SWT.KeyDown, this::onKeyDown); + + treeHBar.addListener(SWT.Selection, e -> redraw(size.tree)); + tableHBar.addListener(SWT.Selection, e -> redraw(size.table)); + commonVBar.addListener(SWT.Selection, e -> redraw(Area.FULL)); models.capture.addListener(this); models.commands.addListener(this); - models.contexts.addListener(this); + models.profile.addListener(this); addListener(SWT.Dispose, e -> { + context.dispose(); + tree.dispose(); + images.dispose(); + models.settings.writeUi().setCommandSplitterRatio(layout.getRatio()); + models.capture.removeListener(this); models.commands.removeListener(this); - models.contexts.removeListener(this); + models.profile.removeListener(this); }); search.addListener(Events.Search, e -> search(e.text, (e.detail & Events.REGEX) != 0)); - selectionHandler = new SelectionHandler(LOG, tree.getControl()) { + selectionHandler = new SelectionHandler(LOG, this) { @Override protected void updateModel(Event e) { models.analytics.postInteraction(View.Commands, ClientAction.Select); - CommandStream.Node node = tree.getSelection(); - if (node != null) { - CommandIndex index = node.getIndex(); - if (index == null) { - models.commands.load(node, () -> models.commands.selectCommands(node.getIndex(), false)); - } else { - models.commands.selectCommands(index, false); - } - } + updateSelection(true); } }; - Menu popup = new Menu(tree.getControl()); - Widgets.createMenuItem(popup, "&Edit", SWT.MOD1 + 'E', e -> { - CommandStream.Node node = tree.getSelection(); - if (node != null && node.getData() != null && node.getCommand() != null) { - widgets.editor.showEditPopup(getShell(), lastCommand(node.getData().getCommands()), - node.getCommand(), node.device); + loadingRepainter = () -> { + if (!isDisposed()) { + updateSize(true, 0); + redraw(size.table); } - }); - tree.setPopupMenu(popup, node -> - node.getData() != null && node.getCommand() != null && - CommandEditor.shouldShowEditPopup(node.getCommand())); + }; + previewRepainter = () -> { + if (!isDisposed()) { + redraw(Area.FULL); // TODO + } + }; + + updateSize(false, 0); + } - tree.registerAsCopySource(widgets.copypaste, node -> { - models.analytics.postInteraction(View.Commands, ClientAction.Copy); - Service.CommandTreeNode data = node.getData(); - if (data == null) { - // Copy before loaded. Not ideal, but this is unlikely. - return new String[] { "Loading..." }; + private void onSashMoved(Event e) { + Rectangle area = getClientArea(); + if (area.width < 2 * MIN_CHILD_SIZE + SASH_WIDTH) { + e.doit = false; + return; + } + + e.x = Math.max(MIN_CHILD_SIZE, Math.min(area.width - SASH_WIDTH - MIN_CHILD_SIZE, e.x)); + if (e.x != ((Sash)e.widget).getBounds().x) { + layout.onSashMoved(e.x, area.width); + layout(); + redraw(); + } + } + + private void onPaint(Event e) { + long start = System.nanoTime(); + Rectangle clip = e.gc.getClipping(); + e.gc.fillRectangle(clip); + try (RenderContext ctx = context.newContext(e.gc)) { + ctx.withClipAndTranslation(size.tree, () -> { + drawTree(ctx); + }); + ctx.withClipAndTranslation(size.table, () -> { + drawTable(ctx); + }); + if (showImagePreview) { + ctx.trace("ImagePreview", () -> drawImagePreview(ctx)); + } + if (columnTooltip != null) { + ctx.trace("ColumnTooltip", () -> columnTooltip.render(ctx)); + } + if (valueTooltip != null) { + ctx.trace("ValueTooltip", () -> valueTooltip.render(ctx)); } + } + long end = System.nanoTime(); + LOG.log(Level.FINE, clip + " (" + (end - start) / 1000000.0 + ")"); + } - StringBuilder result = new StringBuilder(); - if (data.getGroup().isEmpty() && data.hasCommands()) { - result.append(data.getCommands().getTo(0)).append(": "); - API.Command cmd = node.getCommand(); - if (cmd == null) { - // Copy before loaded. Not ideal, but this is unlikely. - result.append("Loading..."); + private void onMouseDown(Event e) { + if (e.widget instanceof Control) { + ((Control)e.widget).setFocus(); + } + } + + private void onMouseUp(Event e) { + double y = e.y - size.tree.y + commonVBar.getSelection(); + if (y >= size.headerHeight) { + int rowIdx = size.getRow(y - size.headerHeight); + if (rowIdx < tree.getNumRows()) { + if (e.count == 2) { + if (tree.toggle(rowIdx, this::initRows)) { + updateSize(false, rowIdx + 1); + updateHover(e.x, e.y); + redraw(Area.FULL); + } } else { - result.append(Formatter.toString(cmd, models.constants::getConstants)); + selectRow(rowIdx); + if (size.tree.contains(e.x, e.y)) { + TreeState.Row row = tree.getRow(rowIdx); + double x = e.x - size.tree.x + treeHBar.getSelection(); + double cx = x - row.getCarretX(); + if (cx >= -IMAGE_PADDING / 2 && cx < CARRET_X_SIZE + IMAGE_PADDING / 2 && + row.node.getChildCount() > 0) { + if (tree.toggle(rowIdx, this::initRows)) { + updateHover(e.x, e.y); + updateSize(false, rowIdx + 1); + } + } else { + Path.Any follow = getFollow(x); + if (follow != null) { + models.follower.onFollow(follow); + } + } + } + redraw(Area.FULL); + } + } + } + } + + private void onMouseWheel(Event e) { + int height = (int)(size.height + Y_PADDING); + int areaHeight = (int)(size.tree.h - size.headerHeight); + if (areaHeight < height) { + int current = commonVBar.getSelection(); + int selection = Math.max(0, Math.min(current - e.count * 10, height - areaHeight)); + if (current != selection) { + commonVBar.setSelection(selection); + redraw(Area.FULL); + } + } + + updateHover(e.x, e.y); + } + + private void onMouseHorizontalWheel(Event e) { + double areaWidth, width; + Slider slider; + if (size.tree.contains(e.x, e.y)) { + areaWidth = size.tree.w; + width = size.treeWidth; + slider = treeHBar; + } else { + areaWidth = size.table.w; + width = size.tableWidth; + slider = tableHBar; + } + if (areaWidth < width) { + int current = slider.getSelection(); + int selection = Math.max(0, Math.min(current - e.count * 10, (int)(width - areaWidth))); + if (current != selection) { + slider.setSelection(selection); + redraw(Area.FULL); + } + } + + updateHover(e.x, e.y); + } + + private void onKeyDown(Event e) { + switch (e.keyCode) { + case SWT.ARROW_DOWN: + if (selectedRow < 0) { + selectRow(0); + } else if (selectedRow < tree.getNumRows() - 1) { + selectRow(selectedRow + 1); + } + redraw(Area.FULL); + break; + case SWT.ARROW_UP: + if (selectedRow < 0) { + selectRow(tree.getNumRows() - 1); + } else if (selectedRow > 0) { + selectRow(selectedRow - 1); + } + redraw(Area.FULL); + break; + case SWT.ARROW_RIGHT: + if (selectedRow >= 0 && selectedRow < tree.getNumRows()) { + TreeState.Row row = tree.getRow(selectedRow); + if (row.node.getChildCount() > 0 && !tree.isExpanded(row)) { + if (tree.toggle(selectedRow, this::initRows)) { + updateSize(false, selectedRow + 1); + updateHover(); + redraw(Area.FULL); + } + } + } + break; + case SWT.ARROW_LEFT: + if (selectedRow >= 0 && selectedRow < tree.getNumRows()) { + TreeState.Row row = tree.getRow(selectedRow); + if (tree.isExpanded(row)) { + if (tree.toggle(selectedRow, this::initRows)) { + updateSize(false, selectedRow + 1); + updateHover(); + redraw(Area.FULL); + } + } + } + break; + case '\r': + case ' ': + if (selectedRow >= 0 && selectedRow < tree.getNumRows()) { + if (tree.toggle(selectedRow, this::initRows)) { + updateSize(false, selectedRow + 1); + updateHover(); + redraw(Area.FULL); + } } - } else { - result.append(data.getCommands().getFrom(0)).append(": ").append(data.getGroup()); } - return new String[] { result.toString() }; - }, true); } private void search(String text, boolean regex) { models.analytics.postInteraction(View.Commands, ClientAction.Search); CommandStream.Node parent = models.commands.getData(); if (parent != null && !text.isEmpty()) { - CommandStream.Node selection = tree.getSelection(); - if (selection != null) { - parent = selection; + if (selectedRow >= 0 && selectedRow < tree.getNumRows()) { + parent = tree.getRow(selectedRow).node; } searchController.start().listen( MoreFutures.transformAsync(models.commands.search(parent, text, regex), - r -> getTreePath(models.commands.getData(), Lists.newArrayList(), + r -> models.commands.getTreePath(models.commands.getData(), Lists.newArrayList(), r.getCommandTreeNode().getIndicesList().iterator())), - new UiCallback(tree, LOG) { - @Override - protected TreePath onRpcThread(Rpc.Result result) - throws RpcException, ExecutionException { - return result.get(); - } + new UiCallback(this, LOG) { + @Override + protected TreePath onRpcThread(Rpc.Result result) + throws RpcException, ExecutionException { + return result.get(); + } - @Override - protected void onUiThread(TreePath result) { - select(result); - } - }); + @Override + protected void onUiThread(TreePath result) { + select(result); + } + }); } } @@ -204,249 +509,1299 @@ public Control getControl() { @Override public void reinitialize() { - updateTree(false); + if (!models.capture.isLoaded()) { + onCaptureLoadingStart(false); + } else { + if (!models.profile.isLoaded()) { + onProfileLoadingStart(); + } + if (models.commands.isLoaded()) { + onCommandsLoaded(); + } + } } @Override public void onCaptureLoadingStart(boolean maintainState) { - updateTree(true); + tableMessage = Loadable.Message.loading(Messages.LOADING_CAPTURE); + updateSize(true, 0); + redraw(Area.FULL); } @Override public void onCaptureLoaded(Loadable.Message error) { if (error != null) { - loading.showMessage(Error, Messages.CAPTURE_LOAD_FAILURE); + tableMessage = error; + updateSize(true, 0); + redraw(Area.FULL); } } @Override public void onCommandsLoaded() { - updateTree(false); + tree.reset(models.commands.getData(), this::initRows); + updateSize(false, 0); + redraw(Area.FULL); } @Override public void onCommandsSelected(CommandIndex index) { - selectionHandler.updateSelectionFromModel(() -> getTreePath(index).get(), tree::setSelection); + selectionHandler.updateSelectionFromModel( + () -> models.commands.getTreePath(index.getNode()).get(), + path -> { + TreeState.Row current = tree.getRootRow(); + for (int i = 1; i < path.getSegmentCount(); i++) { + if (!tree.isExpanded(current)) { + tree.expand(current.index, current, this::initRows); + } + int childIdx = ((CommandStream.Node)path.getSegment(i)).getChildIndex(); + if (childIdx < 0) { + LOG.log(WARNING, "Couldn't find requested selection child " + path.getSegment(i) + + " in " + current.node); + break; + } + current = tree.getChildRow(current, childIdx); + } + + selectedRow = current.index; + updateSelection(false); + updateSize(false, 0); + redraw(Area.FULL); + }); } @Override - public void onContextsLoaded() { - updateTree(false); + public void onProfileLoadingStart() { + tree.resetColumns(); + tableMessage = Loadable.Message.loading(Messages.LOADING_PROFILE); + updateSize(true, 0); + redraw(Area.FULL); } @Override - public void onContextSelected(FilteringContext context) { - updateTree(false); + public void onProfileLoaded(Loadable.Message error) { + if (error != null) { + tableMessage = error; + } else { + // Update the group buttons. + counterGroupButtons.clear(); + counterGroupButtons.addLabel("Counters:"); + List groups = models.profile.getData().getCounterGroups(); + for (Service.ProfilingData.CounterGroup group : groups) { + counterGroupButtons.addButton(group.getLabel(), e -> { + selectCounterGroup(group); + updateSize(false, 0); + redraw(Area.FULL); + }); + } + counterGroupButtons.addButton("All", e -> { + selectAllCounters(); + updateSize(false, 0); + redraw(Area.FULL); + }); + counterGroupButtons.addButton("Custom", e -> { + GpuCountersDialog dialog = new GpuCountersDialog( + getShell(), widgets.theme, getCounterSpecs(), tree.getEnabledMetricIds()); + if (dialog.open() == Window.OK) { + tree.clearColumns(); + selectCounters(dialog.getSelectedIds()); + updateSize(false, 0); + redraw(Area.FULL); + } + }); + counterGroupButtons.selectButton(0); + counterGroupButtons.requestLayout(); + + if (groups.isEmpty()) { + selectAllCounters(); + } else { + selectCounterGroup(groups.get(0)); + } + } + + updateSize(false, 0); + redraw(Area.FULL); + } + + private void selectCounterGroup(Service.ProfilingData.CounterGroup counterGroup) { + tree.clearColumns(); + models.profile.getData().getGpuPerformance().getMetricsList().stream() + .filter(m -> isStaticAnalysisCounter(m) || m.getCounterGroupIdsList().contains(counterGroup.getId())) + .sorted((m1, m2) -> { + if (isStaticAnalysisCounter(m1) != isStaticAnalysisCounter(m2)) { + return isStaticAnalysisCounter(m1) ? -1 : 1; + } + return Integer.compare(m1.getId(), m2.getId()); + }) + .map(TreeState.Column::new) + .forEach(tree::addColumn); + } + + private void selectAllCounters() { + tree.clearColumns(); + models.profile.getData().getGpuPerformance().getMetricsList().stream() + .sorted((m1, m2) -> { + if (isStaticAnalysisCounter(m1) != isStaticAnalysisCounter(m2)) { + return isStaticAnalysisCounter(m1) ? -1 : 1; + } + return Integer.compare(m1.getId(), m2.getId()); + }) + .map(TreeState.Column::new) + .forEach(tree::addColumn); + } + + private void selectCounters(Collection counterIds) { + Map metrics = + models.profile.getData().getGpuPerformance().getMetricsList().stream() + .collect(Collectors.toMap(m -> m.getId(), identity())); + counterIds.stream() + .map(metrics::get) + .filter(Objects::nonNull) + .map(TreeState.Column::new) + .forEach(tree::addColumn); + } + + private List getCounterSpecs() { + if (!models.profile.isLoaded()) { + return Collections.emptyList(); + } + // To reuse the existing GpuCountersDialog class for displaying, forge GpuCounterSpec instances + // with the minimum data requirement that is needed and referenced in GpuCountersDialog. + return models.profile.getData().getGpuPerformance().getMetricsList().stream() + .sorted((m1, m2) -> { + if (isStaticAnalysisCounter(m1) != isStaticAnalysisCounter(m2)) { + return isStaticAnalysisCounter(m1) ? -1 : 1; + } + return Integer.compare(m1.getId(), m2.getId()); + }) + .map(metric -> GpuProfiling.GpuCounterDescriptor.GpuCounterSpec.newBuilder() + .setName(metric.getName()) + .setDescription(metric.getDescription()) + .setCounterId(metric.getId()) + .setSelectByDefault(metric.getSelectByDefault() || isStaticAnalysisCounter(metric)) + .build()) + .collect(toList()); } - private void updateTree(boolean assumeLoading) { - if (assumeLoading || !models.commands.isLoaded()) { - loading.startLoading(); - tree.setInput(null); + private static boolean isStaticAnalysisCounter(Service.ProfilingData.GpuCounters.Metric metric) { + return metric.getType() != Service.ProfilingData.GpuCounters.Metric.Type.Hardware; + } + + protected void updateSize(boolean onlyLoadingMessage, int startRow) { + double height = 0; + if (!models.profile.isLoaded()) { + size.message = widgets.loading.measure(context, tableMessage); + size.tableWidth = Math.ceil(size.message.w); + height = size.message.h; + if (onlyLoadingMessage) { + size.height = Math.max(size.height, height); + updateScrollbars(); + return; + } + + // Default header height based on a random/typical string. + size.headerHeight = context.measure(Style.Bold, "Agi").h + HEADER_MARGIN; + } else if (onlyLoadingMessage) { + updateScrollbars(); return; + } else { + if (size.columnX.length < tree.getNumColumns() + 1) { + size.columnX = new double[tree.getNumColumns() + 1]; + } + double width = 0, headerHeight = 0; + for (int i = 0; i < tree.getNumColumns(); i++) { + size.columnX[i] = width; + Size column = context.measure(Style.Bold, tree.getColumn(i).metric.getName()); + width += Math.ceil(Math.max(100, column.w) + COLUMN_SPACING); + headerHeight = Math.max(headerHeight, column.h); + } + size.columnX[tree.getNumColumns()] = width; + size.tableWidth = width; + size.headerHeight = Math.ceil(headerHeight + HEADER_MARGIN); } - loading.stopLoading(); - tree.setInput(models.commands.getData()); - if (models.commands.getSelectedCommands() != null) { - onCommandsSelected(models.commands.getSelectedCommands()); + double y = startRow == 0 ? 0 : size.rowY[startRow], width = 0; + if (size.rowY.length < tree.getNumRows() + 1) { + size.rowY = Arrays.copyOf(size.rowY, tree.getNumRows() + 1); + } + Size loadingSize = context.measure(Fonts.Style.Normal, "Loading..."); + for (int i = startRow; i < tree.getNumRows(); i++) { + TreeState.Row row = tree.getRow(i); + size.rowY[i] = y; + Size text = (row.size != null) ? row.size : loadingSize; + width = Math.max(width, row.depth * TREE_INDENT + CARRET_X_SIZE + IMAGE_SIZE + IMAGE_PADDING + + text.w + COLUMN_SPACING); + y += Math.ceil(Math.max(text.h, IMAGE_SIZE) + ROW_SPACING); } + size.rowY[tree.getNumRows()] = y++; + + height = Math.max(height, y); + size.treeWidth = Math.ceil(width + COLUMN_SPACING); + size.height = height; + + updateScrollbars(); } - private ListenableFuture getTreePath(CommandIndex index) { - CommandStream.Node root = models.commands.getData(); - ListenableFuture result = getTreePath(root, Lists.newArrayList(root), - index.getNode().getIndicesList().iterator()); - if (index.isGroup()) { - // Find the deepest group/node in the path that is not the last child of its parent. - result = MoreFutures.transform(result, path -> { - while (path.getSegmentCount() > 0) { - CommandStream.Node node = (CommandStream.Node)path.getLastSegment(); - if (!node.isLastChild()) { - break; - } - path = path.getParentPath(); - } - return path; - }); + private void updateScrollbars() { + int treeWidth = (int)size.treeWidth; + int tableWidth = (int)size.tableWidth; + if (size.tree.w >= treeWidth) { + treeHBar.setValues(0, 0, treeWidth, treeWidth, 1, 10); + } else { + int selection = Math.min(treeHBar.getSelection(), (int)(treeWidth - size.tree.w)); + treeHBar.setValues(selection, 0, treeWidth, (int)size.tree.w, 1, 10); + } + if (size.table.w >= tableWidth) { + tableHBar.setValues(0, 0, tableWidth, tableWidth, 1, 10); + } else { + int selection = Math.min(tableHBar.getSelection(), (int)(tableWidth - size.table.w)); + tableHBar.setValues(selection, 0, tableWidth, (int)size.table.w, 1, 10); + } + + int height = (int)(size.height + Y_PADDING); + int areaHeight = (int)(size.tree.h - size.headerHeight); + if (areaHeight >= height) { + commonVBar.setValues(0, 0, height, height, 1, 10); + } else { + int selection = Math.min(commonVBar.getSelection(), height - areaHeight); + commonVBar.setValues(selection, 0, height, areaHeight, 1, 10); } - return result; } - private ListenableFuture getTreePath( - CommandStream.Node node, List path, Iterator indices) { - ListenableFuture load = models.commands.load(node); - if (!indices.hasNext()) { - TreePath result = new TreePath(path.toArray()); - // Ensure the last node in the path is loaded. - return (load == null) ? Futures.immediateFuture(result) : - MoreFutures.transform(load, ignored -> result); + private void redraw(Area area) { + if (!area.isEmpty()) { + Rectangle r = getClientArea(); + if (area != Area.FULL) { + r.x = Math.max(0, Math.min(r.width - 1, (int)Math.floor(area.x))); + r.y = Math.max(0, Math.min(r.height - 1, (int)Math.floor(area.y))); + r.width = Math.max(0, Math.min(r.width - r.x, (int)Math.ceil(area.w + (area.x - r.x)))); + r.height = Math.max(0, Math.min(r.height - r.y, (int)Math.ceil(area.h + (area.y - r.y)))); + } + redraw(r.x, r.y, r.width, r.height, false); } - return (load == null) ? getTreePathForLoadedNode(node, path, indices) : - MoreFutures.transformAsync(load, loaded -> getTreePathForLoadedNode(loaded, path, indices)); } - private ListenableFuture getTreePathForLoadedNode( - CommandStream.Node node, List path, Iterator indices) { - int index = indices.next().intValue(); + private void redrawTree(Area area) { + area = area.translate( + size.tree.x - treeHBar.getSelection(), size.tree.y - commonVBar.getSelection()); + redraw(area.intersect(size.tree)); + } - CommandStream.Node child = node.getChild(index); - path.add(child); - return getTreePath(child, path, indices); + private void redrawRow(int row) { + if (row >= 0) { + redraw(Area.FULL); // TODO + } } - private static class Tree extends LinkifiedTreeWithImages { - private static final float COLOR_INTENSITY = 0.15f; + private void drawTree(RenderContext ctx) { + ctx.setBackgroundColor(SWT.COLOR_LIST_BACKGROUND); + ctx.fillRect(0, 0, size.tree.w, size.tree.h); - protected final Models models; - private final Widgets widgets; - private final Map threadBackgroundColors = Maps.newHashMap(); + drawTreeHeader(ctx); + ctx.withClipAndTranslation(0, size.headerHeight, size.tree.w, size.height, + -treeHBar.getSelection(), size.headerHeight - commonVBar.getSelection(), () -> { + drawTreeGrid(ctx); + drawTreeRows(ctx); + }); + } + + private void drawTreeHeader(RenderContext ctx) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_BACKGROUND); + ctx.drawLine(0, size.headerHeight - 1, size.tree.w, size.headerHeight - 1); + } - public Tree(Composite parent, Models models, Widgets widgets) { - super(parent, SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI, widgets); - this.models = models; - this.widgets = widgets; + private void drawTreeGrid(RenderContext ctx) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_BACKGROUND); + + double endX = Math.max(size.treeWidth, size.tree.w); + Area clip = ctx.getClip(); + double endY = clip.y + clip.h; + for (int i = size.getRow(clip.y); i <= tree.getNumRows() && size.rowY[i] < endY; i++) { + if (i == selectedRow) { + ctx.setBackgroundColor(SWT.COLOR_LIST_SELECTION); + ctx.fillRect(0, size.rowY[i] + 1, endX, size.rowY[i + 1] - size.rowY[i] - 1); + } else if (i == hoveredRow) { + ctx.setBackgroundColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); + ctx.fillRect(0, size.rowY[i] + 1, endX, size.rowY[i + 1] - size.rowY[i] - 1); + } + ctx.drawLine(0, size.rowY[i], endX, size.rowY[i]); } + } - @Override - protected ContentProvider createContentProvider() { - return new ContentProvider() { - @Override - protected boolean hasChildNodes(CommandStream.Node element) { - return element.getChildCount() > 0; + private void drawTreeRows(RenderContext ctx) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_FOREGROUND); + ctx.setBackgroundColor(SWT.COLOR_WIDGET_FOREGROUND); + + Area clip = ctx.getClip(); + double endY = clip.y + clip.h; + for (int i = size.getRow(clip.y); i < tree.getNumRows() && size.rowY[i] < endY; i++) { + TreeState.Row row = tree.getRow(i); + double x = row.getCarretX(), y = size.rowY[i] + ROW_SPACING / 2; + + if (row.node.getChildCount() > 0 && x + CARRET_X_SIZE > clip.x && x < clip.x + clip.w) { + if (tree.isExpanded(row)) { + double cx = x, cy = y + CARRET_Y_PADDING + (CARRET_SIZE_LONG - CARRET_SIZE_SHORT) / 2; + ctx.path(path -> { + path.moveTo(cx, cy); + path.lineTo(cx + CARRET_SIZE_LONG / 2, cy + CARRET_SIZE_SHORT); + path.lineTo(cx + CARRET_SIZE_LONG, cy); + ctx.drawPath(path, CARRET_STROKE); + }); + } else { + double cx = x + (CARRET_SIZE_LONG - CARRET_SIZE_SHORT) / 2, cy = y + CARRET_Y_PADDING; + ctx.path(path -> { + path.moveTo(cx, cy); + path.lineTo(cx + CARRET_SIZE_SHORT, cy + CARRET_SIZE_LONG / 2); + path.lineTo(cx, cy + CARRET_SIZE_LONG); + ctx.drawPath(path, CARRET_STROKE); + }); } + } + x += CARRET_X_SIZE; - @Override - protected CommandStream.Node[] getChildNodes(CommandStream.Node node) { - return node.getChildren(); + double imageX = x + IMAGE_PADDING / 2; + if (shouldShowImage(row.node) && imageX + IMAGE_SIZE > clip.x && imageX < clip.x + clip.w) { + ImageProvider.ImageInfo info = images.getImage(row.node); + if (info.hasImage()) { + Image image = info.isLoading() ? widgets.loading.getCurrentSmallFrame() : info.imageSmall; + Rectangle bounds = image.getBounds(); + ctx.drawImage(image, + imageX + (IMAGE_SIZE - bounds.width) / 2, y + (IMAGE_SIZE - bounds.height) / 2); + if (info.isLoading()) { + widgets.loading.scheduleForRedraw(() -> { + if (tree.validate(row)) { + redrawTree(new Area(imageX, size.headerHeight + y, IMAGE_SIZE, IMAGE_SIZE)); + } + }); + } } + } + x += IMAGE_SIZE + IMAGE_PADDING; - @Override - protected CommandStream.Node getParentNode(CommandStream.Node child) { - return child.getParent(); + if (x < clip.x + clip.w) { + if (row.hasLabel()) { + if (i == selectedRow) { + ctx.setForegroundColor(SWT.COLOR_LIST_SELECTION_TEXT); + row.drawLabel(ctx, x, y, true); + ctx.setForegroundColor(SWT.COLOR_WIDGET_FOREGROUND); + } else { + row.drawLabel(ctx, x, y, false); + } + } else { + if (i == selectedRow) { + ctx.setForegroundColor(SWT.COLOR_LIST_SELECTION_TEXT); + ctx.drawText(Fonts.Style.Normal, "Loading...", x, y); + ctx.setForegroundColor(SWT.COLOR_WIDGET_FOREGROUND); + } else { + ctx.drawText(Fonts.Style.Normal, "Loading...", x, y); + } + row.load(models.commands, () -> { + if (tree.validate(row)) { + updateRowLabel(row); + Service.CommandTreeNode data = row.node.getData(); + if (data.getNumChildren() > 0 && data.getExpandByDefault() && !tree.isExpanded(row)) { + tree.expand(row.index, row, this::initRows); + } + updateSize(false, row.index); + updateHover(); + redraw(Area.FULL); + } + }); + } + } + } + } + + private void drawTable(RenderContext ctx) { + if (!models.profile.isLoaded()) { + ctx.setBackgroundColor(SWT.COLOR_WIDGET_BACKGROUND); + ctx.fillRect(0, 0, size.table.w, size.table.h); + + ctx.withTranslation(-tableHBar.getSelection(), + (size.message.h < size.table.h) ? 0 : -commonVBar.getSelection(), () -> { + ctx.setForegroundColor(SWT.COLOR_WIDGET_FOREGROUND); + widgets.loading.paint(ctx, 0, 0, + Math.max(size.table.w, size.message.w), Math.max(size.table.h, size.message.h), + tableMessage); + if (tableMessage.type == Loadable.MessageType.Loading) { + widgets.loading.scheduleForRedraw(loadingRepainter); } + }); + return; + } + + ctx.setBackgroundColor(SWT.COLOR_LIST_BACKGROUND); + ctx.fillRect(0, 0, size.table.w, size.table.h); + ctx.withClipAndTranslation(0, 0, size.table.w, size.headerHeight, -tableHBar.getSelection(), 0, () -> { + drawTableHeader(ctx); + }); + ctx.withClipAndTranslation(0, size.headerHeight, size.table.w, size.height, + -tableHBar.getSelection(), size.headerHeight - commonVBar.getSelection(), () -> { + drawTableGrid(ctx); + drawTableRows(ctx); + }); + } + + private void drawTableHeader(RenderContext ctx) { + ctx.setForegroundColor(SWT.COLOR_LIST_FOREGROUND); - @Override - protected boolean isLoaded(CommandStream.Node element) { - return element.getData() != null; + Area clip = ctx.getClip(); + double end = clip.x + clip.w; + for (int i = size.getColumn(clip.x); i < tree.getNumColumns() && size.columnX[i] < end; i++) { + ctx.setForegroundColor(SWT.COLOR_LIST_FOREGROUND); + ctx.drawText(Fonts.Style.Normal, tree.getColumn(i).metric.getName(), + size.columnX[i] + COLUMN_SPACING / 2, HEADER_MARGIN / 2); + if (i > 0) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_BACKGROUND); + ctx.drawLine(size.columnX[i], 0, size.columnX[i], size.headerHeight); + } + } + ctx.setForegroundColor(SWT.COLOR_WIDGET_BACKGROUND); + ctx.drawLine(0, size.headerHeight - 1, size.tableWidth, size.headerHeight - 1); + } + + private void drawTableGrid(RenderContext ctx) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_BACKGROUND); + + ctx.trace("y-grid", () -> { + Area clip = ctx.getClip(); + double endY = clip.y + clip.h; + for (int i = size.getRow(clip.y); i <= tree.getNumRows() && size.rowY[i] < endY; i++) { + if (i == selectedRow) { + ctx.setBackgroundColor(SWT.COLOR_LIST_SELECTION); + ctx.fillRect(0, size.rowY[i] + 1, size.tableWidth, size.rowY[i + 1] - size.rowY[i] - 1); + } else if (i == hoveredRow) { + ctx.setBackgroundColor(SWT.COLOR_WIDGET_LIGHT_SHADOW); + ctx.fillRect(0, size.rowY[i] + 1, size.tableWidth, size.rowY[i + 1] - size.rowY[i] - 1); } + ctx.drawLine(0, size.rowY[i], size.tableWidth, size.rowY[i]); + } + }); + + ctx.trace("x-grid", () -> { + Area clip = ctx.getClip(); + double endX = clip.x + clip.w; + for (int i = Math.max(1, size.getColumn(clip.x)); + i <= tree.getNumColumns() && size.columnX[i] < endX; i++) { + ctx.drawLine(size.columnX[i], 0, size.columnX[i], size.headerHeight + size.height); + } + }); + } - @Override - protected void load(CommandStream.Node node, Runnable callback) { - models.commands.load(node, callback); + private void drawTableRows(RenderContext ctx) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_FOREGROUND); + Area clip = ctx.getClip(); + double endX = clip.x + clip.w, endY = clip.y + clip.h; + int startColumn = size.getColumn(clip.x); + for (int i = size.getRow(clip.y); i < tree.getNumRows() && size.rowY[i] < endY; i++) { + TreeState.Row row = tree.getRow(i); + if (row.node.getData() != null) { + if (i == selectedRow) { + ctx.setForegroundColor(SWT.COLOR_LIST_SELECTION_TEXT); + } + for (int j = startColumn; j < tree.getNumColumns() && size.columnX[j] < endX; j++) { + double x = size.columnX[j], w = size.columnX[j + 1] - x; + String text = getValue(row, j).format(true); + Size textSize = ctx.measure(Fonts.Style.Normal, text); + ctx.drawText(Fonts.Style.Normal, text, + x + (w - textSize.w - COLUMN_SPACING / 2), size.rowY[i] + ROW_SPACING / 2); + } + if (i == selectedRow) { + ctx.setForegroundColor(SWT.COLOR_WIDGET_FOREGROUND); } - }; + } } + } - @Override - protected S format( - CommandStream.Node element, S string, Follower.Prefetcher follower) { - Service.CommandTreeNode data = element.getData(); - if (data == null) { - string.append("Loading...", string.structureStyle()); + private Profile.PerfNode.Value getValue(TreeState.Row row, int column) { + Profile.PerfNode perf = models.profile.getData().getPerfNode(row.node.getData()); + if (perf == null) { + return Profile.PerfNode.Value.NULL; + } + return perf.getPerf(tree.getColumn(column).metric); + } + + private void drawImagePreview(RenderContext ctx) { + ImageProvider.ImageInfo info = images.getImage(hoveredNode); + if (!info.isLoading() && !info.hasImage()) { + showImagePreview = false; + return; + } + Image image = info.isLoading() ? widgets.loading.getCurrentFrame() : info.imageLarge; + TreeState.Row row = tree.getRow(hoveredRow); + double y = size.tree.y + size.headerHeight + + (size.rowY[hoveredRow] + size.rowY[hoveredRow + 1]) / 2 - commonVBar.getSelection(); + Tooltip.forImage(image, verticallyCenteredAndConstrained( + row.getImageX() + IMAGE_SIZE + IMAGE_PADDING, y, size.tree.y, size.tree.h)).render(ctx); + + if (info.isLoading()) { + widgets.loading.scheduleForRedraw(previewRepainter); + } + } + + private void initRows(List rows) { + for (TreeState.Row row : rows) { + updateRowLabel(row); + } + } + + private void updateRowLabel(TreeState.Row row) { + if (row.isLoaded()) { + Service.CommandTreeNode data = row.node.getData(); + boolean hovered = row.index == hoveredRow; + LinkableStyledString label = hovered ? LinkableStyledString.create(widgets.theme) : + LinkableStyledString.ignoring(widgets.theme); + Follower.Prefetcher prefetcher = hovered ? lastPrefetcher : nullPrefetcher(); + + if (data.getGroup().isEmpty() && data.hasCommands()) { + Formatter.format(row.node.getCommand(), models.constants::getConstants, + prefetcher::canFollow, label, getCommandStyle(data, label), label.identifierStyle()); } else { - if (data.getGroup().isEmpty() && data.hasCommands()) { - string.append(Formatter.lastIndex(data.getCommands()) + ": ", string.defaultStyle()); - API.Command cmd = element.getCommand(); - if (cmd == null) { - string.append("Loading...", string.structureStyle()); - } else { - Formatter.format(cmd, models.constants::getConstants, follower::canFollow, - string, string.identifierStyle()); + label.append(data.getGroup(), getCommandStyle(data, label)); + long count = data.getNumCommands(); + label.append( + " (" + count + " command" + (count != 1 ? "s" : "") + ")", label.structureStyle()); + } + + label.endLink(); // make sure links are closed. + row.updateLabel(context, label); + } + } + + private Formatter.Style getCommandStyle(Service.CommandTreeNode node, StylingString string) { + if (node.getExperimentalCommandsCount() == 0) { + return string.labelStyle(); + } + + final List experimentCommands = node.getExperimentalCommandsList(); + if (widgets.experiments.areAllCommandsDisabled(experimentCommands)) { + return string.disabledLabelStyle(); + } + + if (widgets.experiments.isAnyCommandDisabled(experimentCommands)) { + return string.semiDisabledLabelStyle(); + } + + return string.labelStyle(); + } + + private void selectRow(int row) { + this.selectedRow = row; + notifyListeners(SWT.Selection, new Event()); + } + + protected void updateSelection(boolean updateModel) { + if (selectedRow >= 0 && selectedRow < tree.getNumRows()) { + TreeState.Row row = tree.getRow(selectedRow); + CommandIndex index = row.node.getIndex(); + if (updateModel) { + if (index == null) { + models.commands.load(row.node, () -> updateSelection(true)); + } else { + models.commands.selectCommands(index, false); + commandIdx.setText(COMMAND_INDEX_DSCRP + row.node.getIndexString()); + commandIdx.requestLayout(); + } + } + models.profile.linkCommandToGpuGroup(row.node.getCommandStart()); + } else { + commandIdx.setText(COMMAND_INDEX_DSCRP); + commandIdx.requestLayout(); + } + } + + private void updateHover() { + Display disp = getDisplay(); + Point cursor = disp.map(null, this, disp.getCursorLocation()); + updateHover(cursor.x, cursor.y); + } + + private void updateHover(int ex, int ey) { + int newHover = -1; + Tooltip newColumnTooltip = null; + CommandStream.Node newHoveredNode = null; + Tooltip newValueTooltip = null; + + double y = ey - size.tree.y; + if (y < 0) { + // Do nothing. + } else if (y < size.headerHeight) { + double x = ex - size.table.x + tableHBar.getSelection(); + int column = (x < 0) ? -1 : size.getColumn(x); + if (column >= 0 && column < tree.getNumColumns()) { + newColumnTooltip = Tooltip.forText(context, tree.getColumn(column).getTooltip(), + horizontallyCenteredAndConstrained( + ex, size.table.y + size.headerHeight, size.table.x, size.table.w)); + } + } else { + y += commonVBar.getSelection() - size.headerHeight; + int rowIdx = size.getRow(y); + if (rowIdx >= 0 && rowIdx < tree.getNumRows()) { + newHover = rowIdx; + TreeState.Row row = tree.getRow(rowIdx); + if (ex >= size.tree.x && ex < size.tree.x + size.tree.w) { + double x = ex - size.tree.x + treeHBar.getSelection() - row.getImageX(); + if (x >= 0 && x < IMAGE_SIZE && shouldShowImage(row.node)) { + newHoveredNode = row.node; + } + } else if (ex >= size.table.x && ex < size.table.x + size.table.w) { + double x = ex - size.table.x + tableHBar.getSelection(); + int column = size.getColumn(x); + if (column >= 0 && column < tree.getNumColumns()) { + Profile.PerfNode.Value value = getValue(row, column); + if (value.hasInterval()) { + newValueTooltip = Tooltip.forFormattedText(context, value.format(false), + standardTooltip(ex + TOOLTIP_OFFSET, ey + TOOLTIP_OFFSET, size.table)); + } + } + } + } + } + + if (newHover != hoveredRow) { + hoveredRow = newHover; + checkHoveredRow(); + redraw(Area.FULL); + } + if (newColumnTooltip != columnTooltip) { + columnTooltip = newColumnTooltip; + redraw(Area.FULL); + } + if (newValueTooltip != valueTooltip) { + valueTooltip = newValueTooltip; + redraw(Area.FULL); + } + if (newHoveredNode != hoveredNode) { + lastScheduledFuture.cancel(true); + showImagePreview = false; + + hoveredNode = newHoveredNode; + if (hoveredNode != null) { + CommandStream.Node scheduledNode = hoveredNode; + lastScheduledFuture = Scheduler.EXECUTOR.schedule(() -> scheduleIfNotDisposed(this, () -> { + if (hoveredNode == scheduledNode) { + showImagePreview = true; + redraw(Area.FULL); } + }), PREVIEW_HOVER_DELAY_MS, TimeUnit.MILLISECONDS); + } else { + redraw(Area.FULL); + } + } + + if (ex < size.tree.x || ex >= size.tree.x + size.tree.w) { + setCursor(null); + } else { + double x = ex - size.tree.x + treeHBar.getSelection(); + setCursor(getFollow(x) != null ? getDisplay().getSystemCursor(SWT.CURSOR_HAND) : null); + } + } + + private void checkHoveredRow() { + if (rowMarkedAsHovered != null) { + if (!tree.validate(rowMarkedAsHovered)) { + rowMarkedAsHovered = null; + lastPrefetcher.cancel(); + lastPrefetcher = nullPrefetcher(); + } else if (rowMarkedAsHovered.index != hoveredRow) { + updateRowLabel(rowMarkedAsHovered); + redrawRow(rowMarkedAsHovered.index); + rowMarkedAsHovered = null; + lastPrefetcher.cancel(); + lastPrefetcher = nullPrefetcher(); + } + } + + if (hoveredRow >= 0 && rowMarkedAsHovered == null) { + TreeState.Row newHoveredRow = tree.getRow(hoveredRow); + rowMarkedAsHovered = newHoveredRow; + lastPrefetcher = models.follower.prepare(rowMarkedAsHovered.node, () -> { + scheduleIfNotDisposed(this, () -> { + updateRowLabel(newHoveredRow); + redrawRow(newHoveredRow.index); + }); + }); + updateRowLabel(rowMarkedAsHovered); + redrawRow(hoveredRow); + } + } + + private Path.Any getFollow(double x) { + Path.Any result = null; + if (hoveredRow >= 0) { + TreeState.Row row = tree.getRow(hoveredRow); + if (row.size != null) { + result = row.getFollow(context, x); + } + } + return result; + } + + private boolean shouldShowImage(CommandStream.Node node) { + return models.images.isReady() && + node.getData() != null && !node.getData().getGroup().isEmpty(); + } + + @Override + public boolean setFocus() { + return forceFocus(); + } + + private static class SizeData { + public Area tree = Area.NONE; + public Area table = Area.NONE; + + public Size message = Size.ZERO; + public double treeWidth = 0, tableWidth = 0; + public double headerHeight = 0; + public double height = 0; + + public double[] columnX = new double[0]; + public double[] rowY = new double[0]; + + public SizeData() { + } + + public int getColumn(double x) { + int first = Arrays.binarySearch(columnX, x); + return (first < 0) ? -first - 2 : first; + } + + public int getRow(double y) { + int first = Arrays.binarySearch(rowY, y); + return (first < 0) ? -first - 2 : first; + } + } + + private static class TreeState { + private final List rows = Lists.newArrayList(); + private final Set expanded = Sets.newIdentityHashSet(); + private final List columns = Lists.newArrayList(); + private Row rootRow; + + public TreeState() { + } + + public void reset(CommandStream.Node root, Consumer> onNewRows) { + for (Row row : rows) { + row.dispose(); + } + rows.clear(); + expanded.clear(); + + rootRow = new Row(root); + expanded.add(rootRow); + expandChildren(rootRow, rows); + onNewRows.accept(rows); + } + + public void resetColumns() { + columns.clear(); + } + + public void dispose() { + for (Row row : rows) { + row.dispose(); + } + rows.clear(); + expanded.clear(); + columns.clear(); + } + + public int getNumRows() { + return rows.size(); + } + + public Row getRow(int idx) { + return rows.get(idx); + } + + public Row getChildRow(Row parent, int idx) { + Row last = parent; + for (int i = 0; i < idx; i++) { + last = rows.get(last.index + 1); + while (isExpanded(last)) { + last = getChildRow(last, last.node.getChildCount() - 1); + } + } + return rows.get(last.index + 1); + } + + public Row getRootRow() { + return rootRow; + } + + public boolean isExpanded(Row row) { + return expanded.contains(row); + } + + public boolean toggle(int idx, Consumer> onNewRows) { + Row row = rows.get(idx); + if (expanded.contains(row)) { + collapse(idx, row); + return true; + } else if (row.node.getChildCount() > 0) { + expand(idx, row, onNewRows); + return true; + } + return false; + } + + protected void expand(int idx, Row parent, Consumer> onNewRows) { + expanded.add(parent); + List newRows = expandChildren(parent, Lists.newArrayList()); + rows.addAll(idx + 1, newRows); + for (int i = idx + 1 + newRows.size(); i < rows.size(); i++) { + rows.get(i).index = i; + } + onNewRows.accept(newRows); + } + + private List expandChildren(Row parent, List newRows) { + int idx = 1; + for (CommandStream.Node node : parent.node.getChildren()) { + Row child = new Row(node, parent.depth + 1, parent.index + idx); + newRows.add(child); + Service.CommandTreeNode data = node.getData(); + if (data != null && data.getNumChildren() > 0 && data.getExpandByDefault()) { + expanded.add(child); + int oldSize = newRows.size(); + expandChildren(child, newRows); + idx += newRows.size() - oldSize + 1; } else { - string.append(Formatter.firstIndex(data.getCommands()) + ": ", string.defaultStyle()); - string.append(data.getGroup(), string.labelStyle()); - long count = data.getNumCommands(); - string.append( - " (" + count + " command" + (count != 1 ? "s" : "") + ")", string.structureStyle()); + idx++; } } - return string; + return newRows; } - @Override - protected Color getBackgroundColor(CommandStream.Node node) { - API.Command cmd = node.getCommand(); - if (cmd == null) { - return null; + private void collapse(int idx, Row parent) { + expanded.remove(parent); + Row last = collapseChildren(parent); + List remove = rows.subList(idx + 1, last.index + 1); + for (Row row : remove) { + row.dispose(); + } + remove.clear(); + for (int i = idx + 1; i < rows.size(); i++) { + rows.get(i).index = i; } + } - long threadId = cmd.getThread(); - Color color = threadBackgroundColors.get(threadId); - if (color == null) { - Control control = getControl(); - RGBA bg = control.getBackground().getRGBA(); - color = new Color(control.getDisplay(), - lerp(getRandomColor(getColorIndex(threadId)), bg.rgb, COLOR_INTENSITY), bg.alpha); - threadBackgroundColors.put(threadId, color); + // Removes any expanded children from the expanded set, and returns the last child. + private Row collapseChildren(Row parent) { + CommandStream.Node[] children = parent.node.getChildren(); + Row last = parent; + for (int i = 0; i < children.length; i++) { + Row child = rows.get(last.index + 1); + if (expanded.contains(child)) { + expanded.remove(child); + last = collapseChildren(child); + } else { + last = child; + } } - return color; + return last; } - private static int getColorIndex(long threadId) { - // TODO: The index should be the i'th thread in use by the capture, not a hash of the - // thread ID. This requires using the list of threads exposed by the service.Capture. - int hash = (int)(threadId ^ (threadId >>> 32)); - hash = hash ^ (hash >>> 16); - hash = hash ^ (hash >>> 8); - return hash & 0xff; + public boolean validate(Row row) { + return row.index < rows.size() && rows.get(row.index) == row; } - @Override - protected boolean shouldShowImage(CommandStream.Node node) { - return models.images.isReady() && - node.getData() != null && !node.getData().getGroup().isEmpty(); + public int getNumColumns() { + return columns.size(); } - @Override - protected ListenableFuture loadImage(CommandStream.Node node, int size) { - return noAlpha(models.images.getThumbnail( - node.getPath(Path.CommandTreeNode.newBuilder()).build(), size, i -> { /*noop*/ })); + public Column getColumn(int idx) { + return columns.get(idx); } - @Override - protected void createImagePopupContents(Shell shell, CommandStream.Node node) { - LoadableImageWidget.forImage( - shell, LoadableImage.newBuilder(widgets.loading) - .forImageData(loadImage(node, THUMB_SIZE)) - .onErrorShowErrorIcon(widgets.theme)) - .withImageEventListener(new LoadableImage.Listener() { - @Override - public void onLoaded(boolean success) { - if (success) { - Widgets.ifNotDisposed(shell,() -> { - Point oldSize = shell.getSize(); - Point newSize = shell.computeSize(SWT.DEFAULT, SWT.DEFAULT); - shell.setSize(newSize); - if (oldSize.y != newSize.y) { - Point location = shell.getLocation(); - location.y += (oldSize.y - newSize.y) / 2; - shell.setLocation(location); + public void clearColumns() { + columns.clear(); + } + + public void addColumn(Column column) { + columns.add(column); + } + + public List getEnabledMetricIds() { + return columns.stream() + .mapToInt(c -> c.metric.getId()) + .boxed() + .collect(toList()); + } + + public static class Row { + public final CommandStream.Node node; + public final int depth; + public int index; + private boolean loading; + private LinkableStyledString label; + private TextLayout layout; + public Size size; + + public Row(CommandStream.Node root) { + this(root, -1, -1); + } + + public Row(CommandStream.Node node, int depth, int index) { + this.node = node; + this.depth = depth; + this.index = index; + this.loading = false; + } + + public boolean isLoaded() { + Service.CommandTreeNode data = node.getData(); + if (data == null) { + return false; + } + return !data.getGroup().isEmpty() || !data.hasCommands() || node.getCommand() != null; + } + + public void load(CommandStream model, Runnable callback) { + if (!loading) { + loading = true; + model.load(node, callback); + } + } + + public void updateLabel(RenderContext.Global ctx, LinkableStyledString newLabel) { + this.label = newLabel; + updateLayout(ctx, false); + size = ctx.measure(layout); + } + + private void updateLayout(Fonts.FontContext ctx, boolean ignoreColors) { + if (layout == null) { + layout = ctx.newTextLayout(); + } + layout.setText(label.getString().getString()); + for (StyleRange range : label.getString().getStyleRanges()) { + if (ignoreColors) { + range.foreground = range.background = null; + } + ctx.applyStyle(layout, range); + } + } + + public boolean hasLabel() { + return label != null; + } + + public void drawLabel(RenderContext ctx, double x, double y, boolean ignoreColors) { + updateLayout(ctx, ignoreColors); + ctx.drawText(layout, x, y); + } + + public Path.Any getFollow(Fonts.TextMeasurer tm, double x) { + x -= getTextX(); + if (x < 0 || x >= size.w) { + return null; + } + int offset = tm.getOffset(layout, x, 5); + return (Path.Any)label.getLinkTarget(offset); + } + + public double getCarretX() { + return COLUMN_SPACING / 2 + depth * TREE_INDENT; + } + + public double getImageX() { + return getCarretX() + CARRET_X_SIZE + IMAGE_PADDING / 2; + } + + public double getTextX() { + return getCarretX() + CARRET_X_SIZE + IMAGE_SIZE + IMAGE_PADDING; + } + + public void dispose() { + if (layout != null) { + layout.dispose(); + } + } + } + + public static class Column { + public final Service.ProfilingData.GpuCounters.Metric metric; + + public Column(Service.ProfilingData.GpuCounters.Metric metric) { + this.metric = metric; + } + + public String getTooltip() { + StringBuilder sb = new StringBuilder().append("\\b").append(metric.getName()); + if (!metric.getDescription().isEmpty()) { + sb.append("\n").append(metric.getDescription()); + } + return sb.toString(); + } + } + } + + private static class ImageProvider { + protected final Widget owner; + private final ImagesModel model; + protected final Map images = Maps.newIdentityHashMap(); + + public ImageProvider(Widget owner, ImagesModel model) { + this.owner = owner; + this.model = model; + } + + private ListenableFuture loadImage(CommandStream.Node node) { + return noAlpha(model.getThumbnail( + node.getPath(Path.CommandTreeNode.newBuilder()).build(), THUMB_SIZE, i -> { /*noop*/ })); + } + + public ImageInfo getImage(CommandStream.Node node) { + ImageInfo image = images.get(node); + if (image == null) { + image = ImageInfo.LOADING; + images.put(node, image); + + Rpc.listen(loadImage(node), new UiCallback(owner, LOG) { + @Override + protected ImageData onRpcThread(Result result) { + try { + return result.get(); + } catch (DataUnavailableException e) { + return null; + } catch (RpcException | ExecutionException e) { + if (!owner.isDisposed()) { + throttleLogRpcError(LOG, "Failed to load image", e); } - }); + return null; + } + } + + @Override + protected void onUiThread(ImageData result) { + ImageInfo info = ImageInfo.NONE; + if (result != null) { + Image large = createNonScaledImage(owner.getDisplay(), result); + Image small = createNonScaledImage(owner.getDisplay(), scaleImage(result, IMAGE_SIZE)); + info = new ImageInfo(large, small); + } + + images.replace(node, info); } + }); + } + return image; + } + + public void dispose() { + for (ImageInfo info : images.values()) { + info.dispose(); + } + images.clear(); + } + + private static class ImageInfo { + public static final ImageInfo LOADING = new ImageInfo(null, null); + public static final ImageInfo NONE = new ImageInfo(null, null); + + public final Image imageLarge; + public final Image imageSmall; + + public ImageInfo(Image imageLarge, Image imageSmall) { + this.imageLarge = imageLarge; + this.imageSmall = imageSmall; + } + + public boolean hasImage() { + return this != NONE; + } + + public boolean isLoading() { + return this == LOADING; + } + + public void dispose() { + if (imageLarge != null) { + imageLarge.dispose(); } - }); + if (imageSmall != null) { + imageSmall.dispose(); + } + } + } + } + + private static class MatchingRowsLayout extends Layout { + private static final int LEFT = 0; + private static final int SASH = 1; + private static final int RIGHT = 2; + private static final int LEFT_H_SLIDER = 3; + private static final int RIGHT_H_SLIDER = 4; + private static final int COMMON_V_SLIDER = 5; + private static final int FOOTER = 6; + + private SizeData size; + private int sliderWidth = 0, sliderHeight = 0; + private int footerHeight = 0; + private double ratio; + + public MatchingRowsLayout(SizeData size, double ratio) { + this.size = size; + this.ratio = ratio; } @Override - protected Follower.Prefetcher prepareFollower(CommandStream.Node node, Runnable cb) { - return models.follower.prepare(node, cb); + protected boolean flushCache(Control control) { + sliderWidth = sliderHeight = 0; + footerHeight = 0; + return true; } @Override - protected void follow(Path.Any path) { - models.follower.onFollow(path); + protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + Control[] children = composite.getChildren(); + Point left = children[LEFT].computeSize(SWT.DEFAULT, hHint, flushCache); + Point right = children[RIGHT].computeSize(SWT.DEFAULT, hHint, flushCache); + Point sliderH = children[LEFT_H_SLIDER].computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point sliderV = children[COMMON_V_SLIDER].computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point footer = children[FOOTER].computeSize(wHint, hHint, flushCache); + sliderWidth = sliderV.x; + sliderHeight = sliderH.y; + footerHeight = footer.y; + + int width = wHint; + if (wHint == SWT.DEFAULT) { + width = Math.max(footer.x, left.x + SASH_WIDTH + right.x + sliderV.x); + } + int height = hHint; + if (hHint == SWT.DEFAULT) { + height = Math.max(left.y, right.y) + sliderH.y + footer.y; + } + return new Point(width, height); } @Override - public void reset() { - super.reset(); - for (Color color : threadBackgroundColors.values()) { - color.dispose(); + protected void layout(Composite composite, boolean flushCache) { + Rectangle area = composite.getClientArea(); + Control[] children = composite.getChildren(); + Point sliderH = children[LEFT_H_SLIDER].computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point sliderV = children[COMMON_V_SLIDER].computeSize(SWT.DEFAULT, SWT.DEFAULT); + Point footer = children[FOOTER].computeSize(area.width, SWT.DEFAULT); + sliderWidth = sliderV.x; + sliderHeight = sliderH.y; + footerHeight = footer.y; + + int width = area.width - sliderWidth - SASH_WIDTH; + int leftWidth = Math.max(0, (int)(ratio * width)); + int rightWidth = Math.max(0, width - leftWidth); + Point left = children[0].computeSize(leftWidth, SWT.DEFAULT, flushCache); + Point right = children[2].computeSize(rightWidth + sliderWidth, SWT.DEFAULT, flushCache); + int height = area.height - sliderHeight - footerHeight; + int sashHeight = Math.max(0, area.height - footerHeight); + int topHeight = Math.max(left.y, right.y); + int vBarY = topHeight + (int)size.headerHeight; + + size.tree = new Area(0, topHeight, leftWidth, Math.max(0, height - topHeight)); + size.table = + new Area(leftWidth + SASH_WIDTH, topHeight, rightWidth, Math.max(0, height - topHeight)); + + if (width <= 0) { + int x = Math.max(0, area.width - sliderWidth); + int w = Math.min(area.width, sliderWidth); + children[LEFT].setBounds(0, 0, 0, 0); + children[SASH].setBounds(0, 0, 0, 0); + children[RIGHT].setBounds(x, 0, w, Math.min(topHeight, Math.max(0, height))); + children[LEFT_H_SLIDER].setBounds(0, 0, 0, 0); + children[RIGHT_H_SLIDER].setBounds(0, 0, 0, 0); + children[COMMON_V_SLIDER].setBounds(x, vBarY, w, Math.max(0, height - vBarY)); + } else if (height <= 0) { + children[LEFT].setBounds(0, 0, leftWidth, 0); + children[SASH].setBounds(leftWidth, 0, SASH_WIDTH, sashHeight); + children[RIGHT].setBounds(leftWidth + SASH_WIDTH, 0, rightWidth + sliderWidth, 0); + children[LEFT_H_SLIDER].setBounds(0, 0, leftWidth, sashHeight); + children[RIGHT_H_SLIDER].setBounds(leftWidth + SASH_WIDTH, 0, rightWidth, sashHeight); + children[COMMON_V_SLIDER].setBounds(0, 0, 0, 0); + } else { + int h = Math.min(topHeight, height); + children[LEFT].setBounds(0, 0, leftWidth, h); + children[SASH].setBounds(leftWidth, 0, SASH_WIDTH, sashHeight); + children[RIGHT].setBounds(leftWidth + SASH_WIDTH, 0, rightWidth + sliderWidth, h); + children[LEFT_H_SLIDER].setBounds(0, height, leftWidth, sliderHeight); + children[RIGHT_H_SLIDER].setBounds(leftWidth + SASH_WIDTH, height, rightWidth, sliderHeight); + children[COMMON_V_SLIDER].setBounds( + area.width - sliderWidth, vBarY, sliderWidth, Math.max(0, height - vBarY)); + } + children[FOOTER].setBounds(0, Math.max(0, area.height - footerHeight), + area.width, Math.min(footerHeight, area.height)); + + ((CommandTree)composite).updateSize(false, 0); + } + + public double getRatio() { + return ratio; + } + + public void onSashMoved(int x, int width) { + width -= SASH_WIDTH + sliderWidth; + if (width < 2 * MIN_CHILD_SIZE) { + ratio = 0.5; + } else if (x < MIN_CHILD_SIZE) { + ratio = (double)MIN_CHILD_SIZE / width; + } else if (x >= width - MIN_CHILD_SIZE) { + ratio = (double)(width - MIN_CHILD_SIZE) / width; + } else { + ratio = (double)x / width; + } + } + + public void onResize(int width) { + width -= SASH_WIDTH + sliderWidth; + if (width <= 2 * MIN_CHILD_SIZE) { + ratio = 0.5; + } else if (ratio * width < MIN_CHILD_SIZE) { + ratio = (double)MIN_CHILD_SIZE / width; + } else if (width - ratio * width < MIN_CHILD_SIZE) { + ratio = (double)(width - MIN_CHILD_SIZE) / width; + } + } + } + + private static class ToggleButtonBar extends Composite { + private final List
element. -type Table struct{ *Element } - -// Td represents an HTML element. -type Tr struct{ *Element } - -// Th represents an HTML ").attr("data-subject", subjId).appendTo(grid); - row.append($("
element. -type Td struct{ *Element } - -// Tr represents an HTML
element. -type Th struct{ *Element } - -// NewTable returns a new Table element. -func NewTable() *Table { - t := &Table{newEl("Table")} - t.Set("border", "1") - return t -} - -// NewTd returns a new Td element. -func NewTd() *Td { return &Td{newEl("Td")} } - -// NewTr returns a new Tr element. -func NewTr() *Tr { return &Tr{newEl("Tr")} } - -// NewTh returns a new Th element. -func NewTh() *Th { return &Th{newEl("Th")} } diff --git a/test/robot/web/client/dom/text.go b/test/robot/web/client/dom/text.go deleted file mode 100644 index 71b48b85e2..0000000000 --- a/test/robot/web/client/dom/text.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package dom - -// Text represents a text node. -type Text struct{ node } - -// Set changes the text content to s. -func (t Text) Set(s string) { - t.Object.Set("textContent", s) -} - -// Get returns the text content -func (t Text) Get() string { - return t.Object.Get("textContent").String() -} diff --git a/test/robot/web/client/dom/video.go b/test/robot/web/client/dom/video.go deleted file mode 100644 index b9e2f7d1f1..0000000000 --- a/test/robot/web/client/dom/video.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (C) 2017 Google Inc. -// -// 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. - -package dom - -import ( - "path/filepath" -) - -type Source struct { - *Element - Src string `js:"src"` - Typ string `js:"type"` -} - -// Video represents an HTML
") - .addClass("subject")); - for (var i = 0; i < 4; i++) { - row.append($("") - .addClass("cell") - .addClass((i == 0) ? "summary" : "clickable") - .append($("") - .attr("width", 48) - .attr("height", 48) - .detectPixelRatio())); - } - row.children().eq(2).click(() => showReplays(row)); - row.children().eq(3).click(() => showReports(row)); - row.children().eq(4).click(() => showTraces(row)); - } - return row; - } - - async function showReplays(row) { - var ts = await traces.getByPackageAndSubject(selection.pkg, row.data("subject")); - viewer.show(ts, t => replays.getByPackageAndTrace(selection.pkg, t.file).then(r => r.json_)); - } - - async function showReports(row) { - var ts = await traces.getByPackageAndSubject(selection.pkg, row.data("subject")); - viewer.show(ts, t => reports.getByPackageAndTrace(selection.pkg, t.file).then(r => r.json_)); - } - - async function showTraces(row) { - var ts = await traces.getByPackageAndSubject(selection.pkg, row.data("subject")); - viewer.show(ts, t => Promise.resolve(t.json_)); - } - - main(); -}); diff --git a/test/robot/web/www/js/griddata.js b/test/robot/web/www/js/griddata.js deleted file mode 100644 index d81b008854..0000000000 --- a/test/robot/web/www/js/griddata.js +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The griddata module handles loading the grid action status data. -define(["xhr"], -function(xhr) { - class Cell { - constructor(json) { - this.json_ = json; - } - - get scheduled() { - return this.json_.Scheduled || 0; - } - - get running() { - return this.json_.Running || 0; - } - - get succeeded() { - return this.json_.Succeeded || 0; - } - - get failed() { - return this.json_.Failed || 0; - } - - get total() { - return this.scheduled + this.running + this.succeeded + this.failed; - } - - add_(c) { - this.json_.Scheduled = this.scheduled + c.scheduled; - this.json_.Running = this.running + c.running; - this.json_.Succeeded = this.succeeded + c.succeeded; - this.json_.Failed = this.failed + c.failed; - return this; - } - } - - class Row { - constructor(json) { - this.json_ = json; - } - - get id() { - return this.json_.ID || ""; - } - - cell(i) { - var cs = this.json_.Cells || []; - return new Cell(cs[i] || {}); - } - - get summary() { - var cs = this.json_.Cells || []; - return cs.reduce((a, b) => a.add_(new Cell(b)), new Cell({})); - } - } - - class Grid { - constructor(json) { - this.json_ = json; - this.columns_ = {}; - this.json_.Columns && this.json_.Columns.forEach((name, idx) => this.columns_[name] = idx); - } - - columnIdx(name) { - var r = this.columns_[name]; - return r == undefined ? -1 : r; - } - - forEachRow(f) { - this.json_.Rows && this.json_.Rows.forEach((r, idx) => f(new Row(r), idx)); - } - } - - function get(pkg) { - return xhr.getJson("/gridData/?pkg=" + encodeURIComponent(pkg)).then(json => new Grid(json || {})); - } - - return { - get: get, - emptyCell: () => new Cell({}), - } -}); diff --git a/test/robot/web/www/js/packages.js b/test/robot/web/www/js/packages.js deleted file mode 100644 index e8a0805506..0000000000 --- a/test/robot/web/www/js/packages.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The packages module provides access to the packages stash. -define(["queriable", "xhr"], -function(queriable, xhr) { - class Package extends queriable.Base { - constructor(json) { - super(json); - } - - get info_() { - return this.json_.information || {}; - } - - get sha() { - return this.info_.cl || ""; - } - - get description() { - return this.info_.description || ""; - } - } - - var pkgs = queriable.new("/packages/", Package) - return Object.assign(pkgs, { - // Returns the package chain rooted at the given package (i.e. all the ancestors). - getChain: (pkgId) => xhr.getJson("/packageChain/?head=" + encodeURIComponent(pkgId)).then(pkgs.toObjs_), - }); -}); diff --git a/test/robot/web/www/js/queriable.js b/test/robot/web/www/js/queriable.js deleted file mode 100644 index 33aa83af4d..0000000000 --- a/test/robot/web/www/js/queriable.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The queriable module provides a base class and constructor that can be used -// by modules providing access to the various queriable stashes. -define(["xhr"], -function(xhr) { - class Base { - constructor(json) { - this.json_ = json; - } - - get id() { - return this.json_.id || ""; - } - } - - function build(url, cls) { - function toObjs(vs) { - // The server returns "null" if nothing is found. - return vs && vs.map(json => new cls(json)) || []; - } - - function onlyOne(vs) { - return vs && vs.length == 1 ? vs[0] : null; - } - - function query(q) { - return xhr.getJson(url + "?q=" + encodeURIComponent(q)).then(toObjs); - } - - return { - // Returns all items in this shelf. - getAll: () => xhr.getJson(url).then(toObjs), - // Returns the item with the given id in this shelf. - get: (id) => query("Id == \"" + id + "\"").then(onlyOne), - // Returns the items that match the given query in this shelf. - query: query, - - url_: url, - toObjs_: toObjs, - onlyOne_: onlyOne, - } - } - - return { - Base: Base, - // Creates a new module using the given URL and class. Use this as the - // return from your module. - new: build, - } -}); diff --git a/test/robot/web/www/js/replays.js b/test/robot/web/www/js/replays.js deleted file mode 100644 index 11ff5dfdbd..0000000000 --- a/test/robot/web/www/js/replays.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The replays module provides access to the replays stash. -define(["actions", "queriable"], -function(actions, queriable) { - class Replay extends actions.Action { - constructor(json) { - super(json); - } - } - - var q = queriable.new("/replays/", Replay); - return Object.assign(q, { - getByPackageAndTrace: (pkg, trace) => - q.query("Input.Package == \"" + pkg + "\" and Input.Trace == \"" + trace + "\"").then(q.onlyOne_), - }); -}); diff --git a/test/robot/web/www/js/reports.js b/test/robot/web/www/js/reports.js deleted file mode 100644 index 4eafaf4454..0000000000 --- a/test/robot/web/www/js/reports.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The reports module provides access to the reports stash. -define(["actions", "queriable"], -function(actions, queriable) { - class Report extends actions.Action { - constructor(json) { - super(json); - } - } - - var q = queriable.new("/reports/", Report); - return Object.assign(q, { - getByPackageAndTrace: (pkg, trace) => - q.query("Input.Package == \"" + pkg + "\" and Input.Trace == \"" + trace + "\"").then(q.onlyOne_), - }); -}); diff --git a/test/robot/web/www/js/selection.js b/test/robot/web/www/js/selection.js deleted file mode 100644 index 7629e3005f..0000000000 --- a/test/robot/web/www/js/selection.js +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The selection module provides a singleton to handle the current user -// selection in the UI and updates and monitors the URL's hash. -define(function() { - class Selection { - constructor() { - this.track_ = ""; - this.trackDefault = ""; - this.pkg_ = ""; - this.pkgDefault = ""; - - this.listeners_ = []; - } - - get track() { - return this.track_ || this.trackDefault; - } - - set track(track) { - this.track_ = track; - } - - get pkg() { - return this.pkg_ || this.pkgDefault; - } - - set pkg(pkg) { - this.pkg_ = pkg; - } - - // Causes the hash of the URL to change and notifies listeners of changes. - commit() { - var hash = [], sel = this; - function value(name) { - var val = sel[name + "_"], dflt = sel[name + "Default"]; - if (val && val != dflt) { - hash.push(name + "=" + encodeURIComponent(val)); - } - } - value("track"); - value("pkg"); - - location.hash = hash.join("@"); - this.notify_(); - } - - // Calls the provided callback where changes to the selection should be made - // and then commits these changes. - update(f) { - f(this); - this.commit(); - } - - // Registers the provided callback as a listener. - listen(f) { - this.listeners_.push(f); - } - - notify_() { - this.listeners_.forEach(l => l(this)); - } - } - - var sel = new Selection(); - function parse() { - var params = {}; - location.hash.substr(1).split("@").forEach(kv => { - kv = kv.split("=", 2); - if (kv.length != 2) { - return; - } - params[kv[0]] = decodeURIComponent(kv[1]); - }); - - var changed = false; - function update(name) { - var value = params[name] || ""; - changed = changed || sel[name] != value; - sel[name] = value; - } - - update("track"); - update("pkg"); - - return changed; - }; - parse(); - - window.addEventListener("hashchange", function() { - if (parse()) { - sel.notify_(); - } - }); - - return sel; -}); diff --git a/test/robot/web/www/js/subjects.js b/test/robot/web/www/js/subjects.js deleted file mode 100644 index d5280d12b9..0000000000 --- a/test/robot/web/www/js/subjects.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The subjects module provides access to the subjects stash. -define(["queriable"], -function(queriable) { - class Subject extends queriable.Base { - constructor(json) { - super(json); - } - - get name() { - var info = this.json_.Information || {}; - var apk = info.APK || {}; - return apk.name || ""; - } - } - - return queriable.new("/subjects/", Subject); -}); diff --git a/test/robot/web/www/js/traces.js b/test/robot/web/www/js/traces.js deleted file mode 100644 index beef4998ed..0000000000 --- a/test/robot/web/www/js/traces.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The traces module provides access to the traces stash. -define(["actions", "queriable"], -function(actions, queriable) { - class Trace extends actions.Action { - constructor(json) { - super(json); - } - - get target() { - return this.json_.target || ""; - } - - get file() { - return this.output_.trace || ""; - } - } - - var q = queriable.new("/traces/", Trace); - return Object.assign(q, { - getByPackageAndSubject: (pkg, subj) => q.query("Input.Package == \"" + pkg + "\" and Input.Subject == \"" + subj + "\""), - }); -}); diff --git a/test/robot/web/www/js/tracks.js b/test/robot/web/www/js/tracks.js deleted file mode 100644 index d92e3f5b95..0000000000 --- a/test/robot/web/www/js/tracks.js +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The tracks module provides access to the tracks stash. -define(["queriable"], -function(queriable) { - class Track extends queriable.Base { - constructor(json) { - super(json); - } - - get name() { - return this.json_.name || ""; - } - - get head() { - return this.json_.head || ""; - } - } - - return queriable.new("/tracks/", Track); -}); diff --git a/test/robot/web/www/js/viewer.js b/test/robot/web/www/js/viewer.js deleted file mode 100644 index 555b68568f..0000000000 --- a/test/robot/web/www/js/viewer.js +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (C) 2018 Google Inc. - * - * 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. - */ -"use strict"; - -// The viewer module provides functions to display a robot Object. -define(["devices", "draw"], -function(devices, draw) { - function linkify(td, val) { - if (/^[0-9a-fA-F]{40}/.test(val)) { - td.append($("") - .attr("href", "/entities/" + val) - .attr("target", "_blank") - .text(val)); - } else { - td.text(val); - } - return td; - } - - function genDom(root, obj, expand) { - var table = $("") - for (var k in obj) { - var row = $("").append($("
").text(k + ":")); - var v = obj[k]; - switch (typeof(v)) { - case "boolean": - case "number": - row.append($("").text(v)); - break; - case "string": - row.append(linkify($(""), v)); - break; - default: - (function() { - var td = $(""); - td.append(draw.icon(expand > 0 ? "expand_less" : "expand_more") - .click(function() { - var el = td.children().eq(1), vis = el.css("display") == "none"; - el.css("display", vis ? "" : "none"); - $(this).text(vis ? "expand_less" : "expand_more"); - })); - row.append(genDom(td, v, expand - 1)); - td.children().eq(1).css("display", expand > 0 ? "" : "none"); - })(); - } - table.append(row); - } - return root.append(table); - } - - function showDropDown(traces, loader) { - var v = $("#viewer").empty(); - if (!traces || !traces.length) { - v.text("no traces"); - return - } - - var dd = $("