aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format2
-rw-r--r--.gitignore6
-rw-r--r--AUTHORS7
-rw-r--r--CMakeLists.txt105
-rw-r--r--CODE_OF_CONDUCT.md93
-rw-r--r--CONTRIBUTING.md28
-rw-r--r--DEPS29
-rw-r--r--LICENSE202
-rw-r--r--README.md97
-rw-r--r--docs/amber_script.md409
-rw-r--r--docs/vk_script.md609
-rw-r--r--docs/vulkan_resource_and_descriptor.md66
-rw-r--r--include/amber/amber.h44
-rw-r--r--include/amber/result.h50
-rw-r--r--samples/CMakeLists.txt40
-rw-r--r--samples/amber.cc177
-rw-r--r--src/CMakeLists.txt84
-rw-r--r--src/amber.cc30
-rw-r--r--src/amber_impl.cc70
-rw-r--r--src/amber_impl.h33
-rw-r--r--src/amberscript/executor.cc34
-rw-r--r--src/amberscript/executor.h37
-rw-r--r--src/amberscript/parser.cc309
-rw-r--r--src/amberscript/parser.h61
-rw-r--r--src/amberscript/parser_test.cc912
-rw-r--r--src/amberscript/pipeline.cc144
-rw-r--r--src/amberscript/pipeline.h85
-rw-r--r--src/amberscript/pipeline_test.cc331
-rw-r--r--src/amberscript/script.cc30
-rw-r--r--src/amberscript/script.h81
-rw-r--r--src/amberscript/shader.cc25
-rw-r--r--src/amberscript/shader.h51
-rw-r--r--src/buffer_data.h24
-rw-r--r--src/cast_hash.h33
-rw-r--r--src/command.cc132
-rw-r--r--src/command.h454
-rw-r--r--src/command_data.cc57
-rw-r--r--src/command_data.h184
-rw-r--r--src/command_data_test.cc61
-rw-r--r--src/datum_type.cc24
-rw-r--r--src/datum_type.h70
-rw-r--r--src/dawn/find_dawn.cmake68
-rw-r--r--src/engine.cc45
-rw-r--r--src/engine.h115
-rw-r--r--src/executor.cc23
-rw-r--r--src/executor.h36
-rw-r--r--src/feature.h83
-rw-r--r--src/format.cc25
-rw-r--r--src/format.h67
-rw-r--r--src/format_data.h173
-rw-r--r--src/make_unique.h30
-rw-r--r--src/parser.cc23
-rw-r--r--src/parser.h38
-rw-r--r--src/pipeline_data.cc25
-rw-r--r--src/pipeline_data.h217
-rw-r--r--src/result.cc29
-rw-r--r--src/result_test.cc41
-rw-r--r--src/script.cc23
-rw-r--r--src/script.h42
-rw-r--r--src/shader_compiler.cc150
-rw-r--r--src/shader_compiler.h43
-rw-r--r--src/shader_compiler_test.cc160
-rw-r--r--src/shader_data.h86
-rw-r--r--src/tokenizer.cc205
-rw-r--r--src/tokenizer.h111
-rw-r--r--src/tokenizer_test.cc554
-rw-r--r--src/value.cc25
-rw-r--r--src/value.h62
-rw-r--r--src/vkscript/command_parser.cc1856
-rw-r--r--src/vkscript/command_parser.h159
-rw-r--r--src/vkscript/command_parser_test.cc3488
-rw-r--r--src/vkscript/datum_type_parser.cc240
-rw-r--r--src/vkscript/datum_type_parser.h39
-rw-r--r--src/vkscript/datum_type_parser_test.cc130
-rw-r--r--src/vkscript/executor.cc154
-rw-r--r--src/vkscript/executor.h35
-rw-r--r--src/vkscript/executor_test.cc988
-rw-r--r--src/vkscript/format_parser.cc505
-rw-r--r--src/vkscript/format_parser.h56
-rw-r--r--src/vkscript/format_parser_test.cc1278
-rw-r--r--src/vkscript/nodes.cc95
-rw-r--r--src/vkscript/nodes.h171
-rw-r--r--src/vkscript/parser.cc395
-rw-r--r--src/vkscript/parser.h64
-rw-r--r--src/vkscript/parser_test.cc548
-rw-r--r--src/vkscript/script.cc55
-rw-r--r--src/vkscript/script.h58
-rw-r--r--src/vkscript/section_parser.cc205
-rw-r--r--src/vkscript/section_parser.h82
-rw-r--r--src/vkscript/section_parser_test.cc299
-rw-r--r--src/vulkan/CMakeLists.txt43
-rw-r--r--src/vulkan/bit_copy.cc166
-rw-r--r--src/vulkan/bit_copy.h82
-rw-r--r--src/vulkan/bit_copy_test.cc382
-rw-r--r--src/vulkan/buffer.cc93
-rw-r--r--src/vulkan/buffer.h60
-rw-r--r--src/vulkan/command.cc131
-rw-r--r--src/vulkan/command.h79
-rw-r--r--src/vulkan/descriptor.cc58
-rw-r--r--src/vulkan/descriptor.h92
-rw-r--r--src/vulkan/device.cc155
-rw-r--r--src/vulkan/device.h76
-rw-r--r--src/vulkan/engine_vulkan.cc278
-rw-r--r--src/vulkan/engine_vulkan.h85
-rw-r--r--src/vulkan/find_vulkan.cmake77
-rw-r--r--src/vulkan/format_data.cc299
-rw-r--r--src/vulkan/format_data.h30
-rw-r--r--src/vulkan/frame_buffer.cc83
-rw-r--r--src/vulkan/frame_buffer.h59
-rw-r--r--src/vulkan/graphics_pipeline.cc590
-rw-r--r--src/vulkan/graphics_pipeline.h121
-rw-r--r--src/vulkan/image.cc151
-rw-r--r--src/vulkan/image.h68
-rw-r--r--src/vulkan/pipeline.cc68
-rw-r--r--src/vulkan/pipeline.h67
-rw-r--r--src/vulkan/resource.cc209
-rw-r--r--src/vulkan/resource.h101
-rw-r--r--src/vulkan/vertex_buffer.cc118
-rw-r--r--src/vulkan/vertex_buffer.h84
-rw-r--r--tests/cases/clear.amber31
-rw-r--r--tests/cases/clear_and_probe_all_wrong_color.expect_fail.amber33
-rw-r--r--tests/cases/clear_and_probe_small_wrong_color.expect_fail.amber33
-rw-r--r--tests/cases/clear_and_probe_too_large_rect.expect_fail.amber33
-rw-r--r--tests/cases/clear_color.amber38
-rw-r--r--tests/cases/clear_color_without_clear_command.expect_fail.amber34
-rw-r--r--tests/cases/multiple_clear_color.amber45
-rwxr-xr-xtests/run_tests.py156
-rw-r--r--third_party/CMakeLists.txt182
-rwxr-xr-xtools/format8
-rwxr-xr-xtools/git-sync-deps275
-rwxr-xr-xtools/update_build_version.py77
131 files changed, 23569 insertions, 0 deletions
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..2fb833a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,2 @@
+# http://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Chromium
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..96b3bed
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+out
+third_party/glslang
+third_party/googletest
+third_party/shaderc
+third_party/spirv-tools
+third_party/spirv-headers
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..8097dd7
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,7 @@
+# This is the list of Amber authors for copyright purposes.
+#
+# This does not necessarily list everyone who has contributed code, since in
+# some cases, their employer may be the copyright holder. To see the full list
+# of contributors, see the revision history in source control.
+
+Google LLC
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..c7c2bd9
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,105 @@
+# Copyright 2018 The Amber 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.
+
+cmake_minimum_required(VERSION 2.8.12)
+
+project(amber)
+enable_testing()
+
+set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
+
+include(CheckIncludeFile)
+include(GNUInstallDirs)
+
+include_directories("${PROJECT_SOURCE_DIR}/include")
+include_directories("${PROJECT_SOURCE_DIR}")
+include_directories("${PROJECT_SOURCE_DIR}/third_party/spirv-tools/include")
+
+include(src/dawn/find_dawn.cmake)
+include(src/vulkan/find_vulkan.cmake)
+
+add_definitions(-DAMBER_ENGINE_VULKAN=$<BOOL:${Vulkan_FOUND}>)
+add_definitions(-DAMBER_ENGINE_DAWN=$<BOOL:${Dawn_FOUND}>)
+
+
+if ("${CMAKE_BUILD_TYPE}" STREQUAL "")
+ message(STATUS "No build type selected, default to Debug")
+ set(CMAKE_BUILD_TYPE "Debug")
+endif()
+
+set(CUSTOM_CXX_FLAGS
+ -std=c++11
+ -Wall
+ -Werror
+ -Wextra)
+
+if(("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") OR
+ (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") AND
+ (NOT CMAKE_CXX_SIMULATE_ID STREQUAL "MSVC")))
+ set(COMPILER_IS_LIKE_GNU TRUE)
+endif()
+
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ set(CUSTOM_CXX_FLAGS
+ ${CUSTOM_CXX_FLAGS}
+ -Weverything
+ -Wno-c++98-compat
+ -Wno-c++98-compat-pedantic
+ -Wno-padded
+ -Wno-switch-enum
+ -Wno-unknown-pragmas
+ -Wno-unknown-warning-option)
+
+elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+ set(CUSTOM_CXX_FLAGS
+ ${CUSTOM_CXX_FLAGS}
+ -Wno-unknown-pragmas
+ -Wpedantic
+ -pedantic-errors)
+elseif(MSVC)
+ set(CUSTOM_CXX_FLAGS
+ ${CUSTOM_CXX_FLAGS}
+ /WX)
+endif()
+
+SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "${CUSTOM_CXX_FLAGS}")
+STRING(REGEX REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+
+function(amber_default_compile_options TARGET)
+ if (${COMPILER_IS_LIKE_GNU})
+ target_compile_options(${TARGET} PRIVATE
+ -fno-exceptions
+ -fno-rtti)
+ endif()
+
+ if (MSVC)
+ # Specify /EHs for exception handling.
+ target_compile_options(${TARGET} PRIVATE /EHs)
+ endif()
+
+ # For MinGW cross compile, statically link to the C++ runtime.
+ # But it still depends on MSVCRT.dll.
+ if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+ if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
+ set_target_properties(${TARGET} PROPERTIES LINK_FLAGS
+ -static
+ -static-libgcc
+ -static-libstdc++)
+ endif()
+ endif()
+endfunction()
+
+add_subdirectory(third_party)
+add_subdirectory(src)
+add_subdirectory(samples)
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..954915d
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,93 @@
+# Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of
+experience, education, socio-economic status, nationality, personal appearance,
+race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, or to ban temporarily or permanently any
+contributor for other behaviors that they deem inappropriate, threatening,
+offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+This Code of Conduct also applies outside the project spaces when the Project
+Steward has a reasonable belief that an individual's behavior may have a
+negative impact on the project or its community.
+
+## Conflict Resolution
+
+We do not believe that all conflict is bad; healthy debate and disagreement
+often yield positive results. However, it is never okay to be disrespectful or
+to engage in behavior that violates the project’s code of conduct.
+
+If you see someone violating the code of conduct, you are encouraged to address
+the behavior directly with those involved. Many issues can be resolved quickly
+and easily, and this gives people more control over the outcome of their
+dispute. If you are unable to resolve the matter for any reason, or if the
+behavior is threatening or harassing, report it. We are dedicated to providing
+an environment where participants feel welcome and safe.
+
+Reports should be directed to dan sinclair <dsinclair@google.com>, the
+Project Steward(s) for Amber. It is the Project Steward’s duty to
+receive and address reported violations of the code of conduct. They will then
+work with a committee consisting of representatives from the Open Source
+Programs Office and the Google Open Source Strategy team. If for any reason you
+are uncomfortable reaching out the Project Steward, please email
+opensource@google.com.
+
+We will investigate every complaint, but you may not receive a direct response.
+We will use our discretion in determining when and how to follow up on reported
+incidents, which may range from not taking action to permanent expulsion from
+the project and project-sponsored spaces. We will notify the accused of the
+report and provide them an opportunity to discuss it before any action is taken.
+The identity of the reporter will be omitted from the details of the report
+supplied to the accused. In potentially harmful situations, such as ongoing
+harassment or threats to anyone's safety, we may take action without notice.
+
+## Attribution
+
+This Code of Conduct is adapted from the Contributor Covenant, version 1.4,
+available at
+https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..db177d4
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,28 @@
+# How to Contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult
+[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more
+information on using pull requests.
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google.com/conduct/).
diff --git a/DEPS b/DEPS
new file mode 100644
index 0000000..d28821b
--- /dev/null
+++ b/DEPS
@@ -0,0 +1,29 @@
+use_relative_paths = True
+
+vars = {
+ 'google_git': 'https://github.com/google',
+ 'khronos_git': 'https://github.com/KhronosGroup',
+
+ 'glslang_revision': 'a08f465d5398518e9a6aeebd4775604a4c10e381',
+ 'googletest_revision': '6463ee81ae7ea8ee3dcaf341cb727d278f8cfe6b',
+ 'shaderc_revision': '92efe4583fcd936252eae61684bcecde8627c9fc',
+ 'spirv_headers_revision': '801cca8104245c07e8cc53292da87ee1b76946fe',
+ 'spirv_tools_revision': '18fe6d59e5e8dd8c5ccf8baa40f57bda838e7dfa',
+}
+
+deps = {
+ 'third_party/googletest': vars['google_git'] + '/googletest.git@' +
+ vars['googletest_revision'],
+
+ 'third_party/glslang': vars['khronos_git'] + '/glslang.git@' +
+ vars['glslang_revision'],
+
+ 'third_party/shaderc': vars['google_git'] + '/shaderc.git@' +
+ vars['shaderc_revision'],
+
+ 'third_party/spirv-headers': vars['khronos_git'] + '/SPIRV-Headers.git@' +
+ vars['spirv_headers_revision'],
+
+ 'third_party/spirv-tools': vars['khronos_git'] + '/SPIRV-Tools.git@' +
+ vars['spirv_tools_revision'],
+}
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a538c6f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,97 @@
+# Amber
+
+Amber is a multi-API shader test framework.
+
+Amber lets you capture and communicate shader bugs with the fluidity and ease of a scripting flow:
+
+* No graphics API programming is required.
+ * WIP: Supports Vulkan and [Dawn][Dawn] graphics APIs.
+* A single text string (or file) maps to a single graphics API pipeline test case. The text includes:
+ * Input data, including buffers and images.
+ * Shaders.
+ * Expectations for the result of running the pipeline.
+* Shaders can be expressed in binary form (as hex), in SPIR-V assembly, or in a higher level shader language.
+* After executing the pipeline, result buffers and images can be saved to output files.
+
+Amber is influenced by [Talvos][Talvos] and [VkRunner][VkRunner].
+The [VkScript](docs/vk_script.md) syntax matches the format used by VkRunner.
+
+This is not an officially supported Google product.
+
+## Requirements
+
+ * Recommended: Configure at least one target graphics API. See below.
+ * Git
+ * CMake
+ * Ninja (or other build tool)
+ * Recommended: Python, for fetching dependencies
+
+
+## Building
+```
+git clone git@github.com:google/amber
+cd amber
+./tools/git-sync-deps
+mkdir -p out/Debug
+cd out/Debug
+cmake -GNinja ../..
+ninja
+```
+
+## Backends
+
+Amber is designed to run against different graphics APIs.
+Amber will build if no graphics API is found, but will only allow verifying the
+syntax of the amber script files.
+
+Currently the Vulkan and Dawn graphics APIs are supported.
+
+### Using Vulkan as a backend
+
+A Vulkan implementation is found by CMake in the following priority order:
+
+ * First: If an enclosing CMake project includes the
+ [Vulkan-Headers][Vulkan-Headers]
+ CMake project, then headers will be picked up from there.
+
+ In this case the CMake variable `Vulkan_LIBRARIES` can name the
+ Vulkan library, or a default of `vulkan` will be used.
+
+ * Second: If you have CMake 3.7 or later, then the Vulkan implementation will
+ be found from a Vulkan SDK as published by LunarG.
+
+ Environment variables:
+ * `VULKAN_SDK` should point to the platform-specific SDK directory
+ that contains the `include` and `lib` directories.
+ Example: `VULKAN_SDK=$HOME/vulkan-macos-1.1.85.0/macOS`
+ * `VK_ICD_FILENAMES` should point to the ICD JSON file.
+ Example: `VK_ICD_FILENAMES=$VULKAN_SDK/etc/vulkan/icd/MoltenVK_icd.json`
+
+### Using Dawn as a backend
+
+We assume you have built [Dawn][Dawn] from source, and have access to both the source
+and build trees. To build a Dawn backend for Amber, set the following CMake variables
+when configuring Amber:
+
+ * `Dawn_INCLUDE_DIR`: The directory containing `dawn/dawn_export.h` (in the source tree).
+ * `Dawn_GEN_INCLUDE_DIR`: The directory containing generated header `dawn/dawncpp.h` (in the build output tree).
+ * `Dawn_LIBRARY_DIR`: The directory containing the `dawn_native` library (in the build output tree).
+
+## Amber Sample
+
+The build will generate an `out/Debug/amber` executable which can be used to
+run amber scripts. The script can be used as
+`out/Debug/amber <path to amber file>`. Where, currently, the amber file is
+in the [VkScript](docs/vk_script.md) format.
+
+## Contributing
+
+Please see the [CONTRIBUTING](CONTRIBUTING.md) and
+[CODE_OF_CONDUCT](CODE_OF_CONDUCT.md) files on how to contribute to Amber.
+
+
+
+[Dawn](https://dawn.googlesource.com/dawn/)
+[Talvos](https://talvos.github.io/)
+[Vulkan-Headers](https://github.com/KhronosGroup/Vulkan-Headers)
+[VkRunner](https://github.com/igalia/vkrunner)
diff --git a/docs/amber_script.md b/docs/amber_script.md
new file mode 100644
index 0000000..b998377
--- /dev/null
+++ b/docs/amber_script.md
@@ -0,0 +1,409 @@
+# AmberScript
+ * DRAFT
+
+This document defines the script input language for the Amber system. The format
+is based on the Talvos format, VkRunner format, and VkScript proposed format.
+
+## Specification
+All amber scripts must start with `#!amber` as the first line. Comments are
+specified by a # character and continue to the end of the line. Keywords are
+case sensitive. All names are made up of ASCII characters, and delimited by
+whitespace.
+
+TODO(dneto): What characters are valid in a name?
+
+### Number literals
+
+Literal numbers are normally presented in decimal form. They are interpreted
+as integers or floating point depending on context: a command parameter is predefined
+as either integral or floating point, or the data type is user-specified (such
+as for buffer data).
+
+Hex values: Whenever an integer is expected, you may use a hexadecimal number, which
+is the characters `0x` followed by hexadecimal digits.
+
+### Shaders
+
+#### Shader Type
+ * vertex
+ * fragment
+ * geometry
+ * tessellation\_evaluation
+ * tessellation\_control
+ * compute
+
+The compute pipeline can only contain compute shaders. The graphics pipeline
+can not contain compute shaders, and must contain a vertex shader and a fragment
+shader.
+
+#### Shader Format
+ * GLSL  (with glslang)
+ * HLSL  (with dxc or glslang if dxc disabled) -- future
+ * SPIRV-ASM (with spirv-as)
+ * SPIRV-HEX (decoded straight to spv)
+ * OPENCL-C (with clspv)  --- potentially? -- future
+
+```
+SHADER vertex <shader_name> PASSTHROUGH
+
+SHADER <shader_type> <shader_name> <shader_format>
+...
+END
+```
+
+### Buffers
+
+#### Data Types
+ * int8
+ * int16
+ * int32
+ * int64
+ * uint8
+ * uint16
+ * uint32
+ * uint64
+ * float
+ * double
+ * vec[2,3,4]\<type>
+ * mat[2,3,4]\<type>    -- useful?
+
+TODO(dneto): Support half-precision floating point.
+
+Sized arrays and structures are not currently representable.
+
+#### Buffer Types
+ * uniform
+ * storage
+ * vertex
+ * index
+ * sampled
+ * storage
+ * color
+ * depth
+
+```
+// Filling the buffer with a given set of data. The values must provide
+// <size_in_bytes> of <type> data. The data can be provided as the type or
+// as a hex value.
+
+BUFFER <buffer_type> <name> DATA_TYPE <type> DATA
+<value>+
+END
+
+BUFFER <buffer_type> <name> DATA_TYPE <type> SIZE <size_in_bytes> <initializer>
+
+BUFFER framebuffer <name> DIMS <width_in_pixels> <height_in_pixels>
+```
+
+#### Buffer Initializers
+Fill the buffer with a single value.
+
+```
+FILL <value>
+```
+
+Fill the buffer with an increasing value from \<start> increasing by \<inc>.
+Floating point data uses floating point addition to generate increasting values.
+Likewise, integer data uses integer addition to generate increasing values.
+
+```
+SERIES <start> <inc>
+```
+
+### Pipelines
+
+#### Pipeline type
+ * compute
+ * graphics
+
+The PIPELINE command creates a pipeline. This can be either compute or graphics.
+Shaders are attached to the pipeline at pipeline creation time.
+
+```
+PIPELINE <pipeline_type> <pipeline_name>
+...
+END
+```
+
+### Pipeline Content
+
+The following commands are all specified within the `PIPELINE` command. If you
+have multiple entry points for a given shader, you'd create multiple pipelines
+each with a different `ENTRY_POINT`.
+
+Bind the entry point to use for a given shader. The default entry point is main.
+
+```
+ ENTRY_POINT <shader_name> <entry_point_name>
+```
+
+Shaders can be added into pipelines with the `ATTACH` call. Shaders may be
+attached to multiple pipelines at the same time.
+
+```
+ ATTACH <name_of_vertex_shader>
+ ATTACH <name_of_fragment_shader>
+```
+
+Set the SPIRV-Tools optimization passes to use for a given shader. The default
+is to run no optimization passes.
+```
+ SHADER_OPTIMIZATION <shader_name>
+ <optimization_name>+
+ END
+```
+
+#### Bindings
+
+##### Topologies
+ * point\_list
+ * line\_list
+ * line\_list\_with\_adjacency
+ * line\_strip
+ * line\_strip\_with\_adjacency
+ * triangle\_list
+ * triangle\_list\_with\_adjacency
+ * triangle\_strip
+ * triangle\_strip\_with\_adjacency
+ * triangle\_fan
+ * patch\_list
+
+Bind a provided framebuffer.
+
+```
+ FRAMEBUFFER <buffer_of_type_framebuffer_name>
+```
+
+Descriptor sets can be bound as:
+
+```
+ DESCRIPTOR_SET <id> BINDING <id> IDX <0> TO <buffer_name>
+```
+
+### Run a pipeline.
+
+```
+RUN <pipeline_name> <x> <y> <z>
+
+RUN <pipeline_name> \
+  DRAW_RECT POS <x_in_pixels> <y_in_pixels> \
+  SIZE <width_in_pixels> <height_in_pixels>
+
+RUN <pipeline_name> \
+  DRAW_ARRAY <indices_buffer> IN <data_buffer> \
+  AS <topology> START_IDX <value> COUNT <value>
+```
+
+### Commands
+```
+CLEAR_COLOR <pipeline> <r (0 - 255)> <g (0 - 255)> <b (0 - 255)>
+
+CLEAR <pipeline>
+```
+
+### Expectations
+
+#### Comparators
+ * EQ
+ * NE
+ * LT
+ * LE
+ * GT
+ * GE
+ * EQ\_RGB
+ * EQ\_RGBA
+
+```
+EXPECT <buffer_name> IDX <x> <y> <comparator> <value>+
+
+EXPECT <framebuffer_name> IDX <x_in_pixels> <y_in_pixels> \
+  SIZE <width_in_pixels> <height_in_pixels> \
+  EQ_RGB <r (0 - 255)> <g (0 - 255)> <b (0 - 255)>
+```
+
+## Examples
+
+### Compute Shader
+```
+#!amber
+# Simple amber compute shader.
+
+SHADER compute kComputeShader GLSL
+#version 450
+
+layout(binding = 3) buffer block {
+  vec2 values[];
+};
+
+void main() {
+  values[gl_WorkGroupID.x + gl_WorkGroupID.y * gl_NumWorkGroups.x] =
+                gl_WorkGroupID.xy;
+}
+END  # shader
+
+BUFFER storage kComputeBuffer TYPE vec2<int32> SIZE 524288 FILL 0
+
+PIPELINE compute kComputePipeline
+ ATTACH kComputeShader
+ DESCRIPTOR_SET 0 BINDING 3 IDX 0 TO kComputeBuffer
+END  # pipeline
+
+RUN kComputePipeline 256 256 1
+
+# Four corners
+EXPECT kComputeBuffer IDX 0 EQ 0 0
+EXPECT kComputeBuffer IDX 2040 EQ 255 0
+EXPECT kComputeBuffer IDX 522240 EQ 0 255
+EXPECT kComputeBuffer IDX 524280 EQ 255 255
+
+# Center
+EXPECT kComputeBuffer IDX 263168 EQ 128 128
+```
+
+### Entry Points
+```
+#!amber
+
+SHADER vertex kVertexShader PASSTHROUGH
+
+SHADER fragment kFragmentShader SPIRV-ASM
+              OpCapability Shader
+          %1 = OpExtInstImport "GLSL.std.450"
+               OpMemoryModel Logical GLSL450
+
+; two entrypoints
+               OpEntryPoint Fragment %red "red" %color
+               OpEntryPoint Fragment %green "green" %color
+
+               OpExecutionMode %red OriginUpperLeft
+               OpExecutionMode %green OriginUpperLeft
+               OpSource GLSL 430
+               OpName %red "red"
+               OpDecorate %color Location 0
+       %void = OpTypeVoid
+          %3 = OpTypeFunction %void
+      %float = OpTypeFloat 32
+    %v4float = OpTypeVector %float 4
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+      %color = OpVariable %_ptr_Output_v4float Output
+    %float_1 = OpConstant %float 1
+    %float_0 = OpConstant %float 0
+  %red_color = OpConstantComposite %v4float %float_1 %float_0 %float_0 %float_1
+%green_color = OpConstantComposite %v4float %float_0 %float_1 %float_0 %float_1
+
+; this entrypoint outputs a red color
+        %red = OpFunction %void None %3
+          %5 = OpLabel
+               OpStore %color %red_color
+               OpReturn
+               OpFunctionEnd
+
+; this entrypoint outputs a green color
+      %green = OpFunction %void None %3
+          %6 = OpLabel
+               OpStore %color %green_color
+               OpReturn
+               OpFunctionEnd
+END  # shader
+
+BUFFER framebuffer kFrameBuffer DIMS 256 256
+
+PIPELINE graphics kRedPipeline
+ ATTACH kVertexShader
+ SHADER_OPTIMIZATION kVertexShader
+ eliminate-dead-branches
+ merge-return
+ eliminate-dead-code-aggressive
+ END
+
+ ATTACH kFragmentShader
+
+ FRAMEBUFFER kFrameBuffer
+ ENTRY_POINT kFragmentShader red
+END  # pipeline
+
+PIPELINE graphics kGreenPipeline
+ ATTACH kVertexShader
+ ATTACH kFragmentShader
+
+ FRAMEBUFFER kFrameBuffer
+ ENTRY_POINT kFragmentShader green
+END  # pipeline
+
+RUN kRedPipeline DRAW_RECT POS 0 0  SIZE 256 256
+RUN kGreenPipeline DRAW_RECT POS 128 128 SIZE 256 256
+
+EXPECT kFrameBuffer IDX 0 0 SIZE 127 127 EQ_RGB 255 0 0
+EXPECT kFrameBuffer IDX 128 128 SIZE 128 128 EQ_RGB 0 255 0
+```
+
+### Buffers
+```
+#!amber
+
+SHADER vertex kVertexShader GLSL
+  #version 430
+
+  layout(location = 0) in vec4 position;
+  layout(location = 1) in vec4 color_in;
+  layout(location = 0) out vec4 color_out;
+
+  void main() {
+    gl_Position = position;
+    color_out = color_in;
+  }
+END  # shader
+
+SHADER fragment kFragmentShader GLSL
+  #version 430
+
+  layout(location = 0) in vec4 color_in;
+  layout(location = 0) out vec4 color_out;
+
+  void main() {
+    color_out = color_in;
+  }
+END  # shader
+
+PIPELINE graphics kGraphicsPipeline
+ ATTACH kVertexShader
+ ATTACH kFragmentShader
+END  # pipeline
+
+BUFFER vertex kData TYPE vec3<int32> DATA
+# Top-left red
+-1 -1  0xff0000ff
+ 0 -1  0xff0000ff
+-1  0 0xff0000ff
+ 0  0 0xff0000ff
+# Top-right green
+ 0 -1  0xff00ff00
+ 1 -1  0xff00ff00
+ 0  0 0xff00ff00
+ 1  0 0xff00ff00
+# Bottom-left blue
+-1  0 0xffff0000
+ 0  0 0xffff0000
+-1  1 0xffff0000
+ 0  1 0xffff0000
+# Bottom-right purple
+ 0  0 0xff800080
+ 1  0 0xff800080
+ 0  1 0xff800080
+ 1  1 0xff800080
+END
+
+BUFFER index kIndices TYPE int32 DATA
+0  1 2    2 1 3
+4  5 6    6 5 7
+8  9 10   10 9 11
+12 13 14   14 13 15
+END
+
+CLEAR_COLOR kGraphicsPipeline 255 0 0 255
+CLEAR kGraphicsPipeline
+
+RUN kGraphicsPipeline \
+  DRAW_ARRAY kIndices IN kBuffer AS triangle_list \
+  START_IDX 0 COUNT 24
+ ```
diff --git a/docs/vk_script.md b/docs/vk_script.md
new file mode 100644
index 0000000..9448b56
--- /dev/null
+++ b/docs/vk_script.md
@@ -0,0 +1,609 @@
+# VkScript
+
+The VkScript format is a clone of the format used by VkRunner as described in
+[1].
+
+# General
+## Comments
+The # symbol can be used to start a comment which extends to the end of the
+line.
+
+## Continuations
+The \ can be used at the end of a line to signal a continuation, the
+new line will be skipped and parsing will treat the following line as a
+continuation of the current line.
+
+## Descriptor Sets and Bindings
+Any command below which accepts a binding will accept either a single integer
+value which will have a descriptor set of 0 and a binding of the value give or
+a string can be provided of the form <set integer>:<binding integer> in which
+case the descriptor set value will be `set` and the binding value will be
+`binding`.
+
+# Sections
+
+The format is broken down into five main sections:
+ * require
+ * shaders
+ * indices
+ * vertex data
+ * test
+
+## Require
+The `require` section lists all of the requirements for the testing environment.
+There are four types of information that can be encoded in the require section.
+
+The _feature_ list contains a list of features that are required in
+order for the test to execute. If a feature is missing an error will be reported
+and the test will fail. The _features_ are listed below in the
+*Available Require Features* section.
+
+The _framebuffer_ and _depthstencil_ commands allow setting the format for the
+given buffer. The valid values are listed below in the *Image Formats*
+section.
+
+The last option is _extensions_. Any string which isn't a _feature_,
+_framebuffer_ or _depthstencil_ is assumed to be an _extension_. The extensions
+must be of the format [a-zA-Z0-9_]+. If the device extension is not available
+we will report it is not available and the test will continue.
+
+#### Require Examples
+```
+[require]
+independentBlend
+VK_KHR_storage_buffer_storage_class
+```
+
+## Shaders
+The shader section allows you to specify the content of the shaders under test.
+This can be done as GLSL, SPIRV-ASM or SPIRV-Hex depending on how the shader is
+formatted. There is also a special *passthrough* vertex shader which can be
+used which just passes the vec4 input location 0 through to the `gl_Position`.
+The shader format is specified in the header after the word `shader`. The
+default is `GLSL`, SPIRV-ASM is specified as `spirv` and SPIRV-Hex as
+`spirv hex`.
+
+The shaders accepted are:
+ * compute
+ * fragment
+ * geometry
+ * tessellation control
+ * tessellation evaulation
+ * vertex
+
+#### Shader examples
+```
+[fragment shader]
+#version 430
+
+layout(location = 0) out vec4 color_out;
+
+void main() {
+ color_out = vec4(1, 2, 3, 4);
+}
+
+```
+
+Other example shader header lines are:
+ * `[fragment shader spirv hex]` -- a hex encoded SPIRV binary fragment shader
+ * `[tessellation evaluation shader spirv]` -- a spirv-asm tessellation evaluation shader
+ * `[vertex shader passthrough]`
+
+## Vertex Data
+The `vertex data` section provides vertex attributes and data for `draw array`
+commands. The data is formated with a header row followed by data rows.
+
+The headers can be provided in one of two forms. The first,
+`attribute_location/format` where `attribute_location` is the location of the
+attribute to be bound. The format is one of the *Image Formats* listed below.
+The second, `attribute_location/gl_type/glsl_type`. The `gl_type` is one of
+the types listed in the *GL Types* section below. The `glsl_type` is one listed
+in the *GLSL Types* section below.
+
+#### Vertex Data example
+```
+[vertex data]
+0/R32G32B32_SFLOAT 1/R8G8B8_UNORM
+-1 -1 0.25 255 0 0 # ending comment
+# Another Row
+0.25 -1 0.25 255 0 255
+```
+
+## Indices
+The `indices` section contains the list of indices to use along with the
+provided `vertex data`. The `indices` are used if the `indexed` option is
+provided to the `draw arrays` command. The indices themselves are a list of
+integer indexes to use.
+
+#### Indices Example
+```
+[indices]
+# comment line
+1 2 3 4 5 6
+# another comment
+7 8 9 10 11 12
+```
+
+## Test
+The test section contains a list of commands which can be executed to perform
+the actual testing. The commands range from setting up pipeline parameters,
+executing compute shaders and probing buffers to verify results.
+
+
+### Draw Rect
+ * `draw rect [ortho] [patch] _x_ _y_ _width_ _height_`
+
+The `draw rect` command draws a rectangle at the given coordinates. The vertices
+are uploaded at location 0 as a `vec3`. The `ortho` modifier scales the
+coordinates to be in the range of [-1,1] instead of [0,window size]. The `patch`
+modifier sets the draw call to use a given topology. Accepted possible
+topology value are listed in *Topologies*. The patch size will be set to 4.
+
+
+### Draw Arrays
+ * `draw arrays [indexed] [instanced] _topology_ _first_vertex_ _vertex_count_ [instance_count]`
+
+The `draw arrays` command uses data from the `vertex data` section when executing
+the draw command. The `topology` is from the *Topologies* list. If the `indexed`
+modifier is provided then the `indices` section data will be used as well.
+
+
+### Compute
+ * `compute _x_ _y_ _z_`
+
+Executes the compute shader with the given `x`, `y`, and `z` parameters.
+
+
+### Shader Entry Point
+ * `_stage_ entrypoint _name_`
+
+Sets the `stage` shader to use the entry point of `name` for subsequent
+executions.
+
+
+### Probe all
+ * `probe all (rgb|rgba) _r_ _g_ _b_ [_a_]
+
+Probes the entire window to verify all pixels are of color r,g,b and optionally
+a. If `rgba` is specified then the `a` parameter is required. If `rgb` is
+specified then the `a` parameter is dis-allowed.
+
+
+### Probe
+ * `[relative] probe [rect] (rgb|rgba) (_x_, _y_[, _width_, _height_]) (_r_, _g_, _b_[, _a_])`
+
+Probes a portion of the window to verify the pixes are of color r,g,b and
+optionally a. If `rgba` is specifed then the `a` parameter is required. If
+`rgb` is specified then the `a` parameter is dis-allowed. If `rect` is specified
+then `width` and `height` are required. If `rect` is not specified then `width`
+and `height` are dis-allowed and a value of 1 will be used for each parameter.
+If the `relative` parameter is provided the coordinates are normalized to
+be in the range [0.0, 1.0].
+
+
+### Probe SSBO
+* `probe ssbo _type_ _binding_ _offset_ _comparison_ _values_+`
+
+Probes the value in the storage buffer at `binding` and `offset` within that
+binding. The `type` is the data type to be probed as seen in the *Data Types*
+section below.
+
+The comparison operators are:
+ * `==` (equal)
+ * `!=` (not equal)
+ * `<` (less than)
+ * '>' (greater than)
+ * `<=` (less or equal)
+ * `>=` (greater or equal)
+ * `~=` (fuzzy equal, for floating point comparisons using `tolerances`)
+
+The `values` provided must be a non-zero multiple of the `type`.
+
+
+### Uniform
+ * `uniform _type_ _offset _values_+`
+
+Sets the push constants at `offset`. The `type` is from the *Data Types*
+section below. The `values` must be a non-zero multiple of the requested
+`type`.
+
+
+### Unifom UBO
+ * `uniform ubo _binding_ _type_ _offset_ _values_+`
+
+Sets the values in the uniform buffer at `binding` and `offset`. The `type`
+is from the *Data Types* section below. The `values` must be a non-zero
+multiple of the requested `type`.
+
+
+### SSBO size
+ * `ssbo _binding_ _size_`
+
+Sets the size of the SSBO at `binding` to `size`.
+
+
+### SSBO subdata
+ * `ssbo _binding_ subdata _type_ _offset_ _values_+`
+
+Sets the value of the buffer at `binding` and `offset`. The `type` is from the
+*Data Types* section below. The `values` must be a non-zero multiple of the
+requested `type`.
+
+
+### Patch Parameters
+ * `patch parameter vertices _count_`
+
+Sets the number of control points for tessellation patches to `count`. Defaults
+to 3.
+
+
+### Tolerance
+ * `tolerance tolerance0 [tolerance1 tolerance2 tolerance3]
+
+The `tolerance` command sets the amount of fuzzyness used when using the `~=`
+comparator. If a single tolerance value is set it is used for every comparison.
+If all four values are set then each `vecN` command will use the first `N`
+tolerance values. Each column of a `matMxN` will also use the first `N`
+tolerances. A tolerance maybe either a number or a percentage `0.01%`.
+
+
+### Clear Color
+ * `clear color _r_ _g_ _b_ _a_`
+
+Sets the clear color. Defaults to (0, 0, 0, 0).
+
+
+### Clear Depth
+ * `clear depth _value_`
+
+Sets the depth clear value. Defaults to 1.0.
+
+
+### Clear Stencil
+ * `clear stencil _value_`
+
+Sets the stencil clear value. Defaults to 0.0.
+
+
+### Clear
+ * `clear`
+
+Clears the framebuffer.
+
+### Pipeline Configuration
+There are a number of pipeline flags which can be set to alter execution. Each
+draw call uses the pipeline configuration that was specified prior to the draw
+call.
+
+The pipeline commands with their accepted data are:
+ * `primitiveRestartEnable <bool>`
+ * `depthClampEnable <bool>`
+ * `rasterizerDiscardEnable <bool>`
+ * `depthBiasEnable <bool>`
+ * `logicOpEnable <bool>`
+ * `blendEnable <bool>`
+ * `depthTestEnable <bool>`
+ * `depthWriteEnable <bool>`
+ * `depthBoundsTestEnable <bool>`
+ * `stencilTestEnable <bool>`
+ * `topology <VkPrimitiveTopology>`
+ * `polygonMode <VkPolygonMode>`
+ * `logicOp <VkLogicOp>`
+ * `frontFace <VkFrontFace>`
+ * `cullMode <VkCullMode>`
+ * `depthBiasConstantFactor <float>`
+ * `depthBiasClamp <float>`
+ * `depthBiasSlopeFactor <float>`
+ * `lineWidth <float>`
+ * `minDepthBounds <float>`
+ * `maxDepthBounds <float>`
+ * `srcColorBlendFactor <VkBlendFactor>`
+ * `dstColorBlendFactor <VkBlendFactor>`
+ * `srcAlphaBlendFactor <VkBlendFactor>`
+ * `dstAlphaBlendFactor <VkBlendFactor>`
+ * `colorBlendOp <VkBlendOp>`
+ * `alphaBlendOp <VkBlendOp>`
+ * `depthCompareOp <VkCompareOp>`
+ * `front.compareOp <VkCompareOp>`
+ * `back.compareOp <VkCompareOp>`
+ * `front.failOp <VkStencilOp>`
+ * `front.passOp <VkStencilOp>`
+ * `front.depthFailOp <VkStencilOp>`
+ * `back.failOp <VkStencilOp>`
+ * `back.passOp <VkStencilOp>`
+ * `back.depthFailOp <VkStencilOp>`
+ * `front.reference <uint32_t>`
+ * `back.reference <uint32_t>`
+ * `colorWriteMask <VkColorComponent bitmask>`
+
+#### Test Example
+```
+[test]
+clear color 1 0.4 0.5 0.2
+clear
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (1.0, 0.4, 0.5, 0.2)
+```
+
+### Data Types
+ * int
+ * uint
+ * int8_t
+ * uint8_t
+ * int16_t
+ * uint16_t
+ * int64_t
+ * uint64_t
+ * float
+ * double
+ * vec
+ * vec[234]
+ * dvec
+ * dvec[234]
+ * ivec
+ * ivec[234]
+ * uvec
+ * uvec[234]
+ * i8vec
+ * i8vec[234]
+ * u8vec
+ * u8vec[234]
+ * i16vec
+ * i16vec[234]
+ * u16vec
+ * u16vec[234]
+ * i64vec
+ * i64vec[234]
+ * u64vec
+ * u64vec[234]
+ * mat
+ * mat[234]x[234]
+ * dmat
+ * dmat[234]x[234]
+
+### Topologies
+ * PATCH_LIST
+ * POINT_LIST
+ * GL_LINE_STRIP_ADJACENCY
+ * GL_LINE_STRIP
+ * GL_LINES
+ * GL_LINES_ADJACENCY
+ * GL_PATCHES
+ * GL_POINTS
+ * GL_TRIANGLE_STRIP
+ * GL_TRIANGLE_FAN
+ * GL_TRIANGLES
+ * GL_TRIANGLES_ADJACENCY
+ * GL_TRIANGLE_STRIP_ADJACENCY
+ * LINE_LIST
+ * LINE_LIST_WITH_ADJACENCY
+ * LINE_STRIP
+ * LINE_STRIP_WITH_ADJACENCY
+ * TRIANGLE_FAN
+ * TRIANGLE_LIST
+ * TRIANGLE_LIST_WITH_ADJACENCY
+ * TRIANGLE_STRIP
+ * TRIANGLE_STRIP_WITH_ADJACENCY
+
+
+### GL Types
+ * byte
+ * ubyte
+ * short
+ * ushort
+ * int
+ * uint
+ * half
+ * float
+ * double
+
+### GLSL Types
+ * int
+ * uint
+ * float
+ * double
+ * vec
+ * vec2
+ * vec3
+ * vec4
+ * dvec
+ * dvec2
+ * dvec3
+ * dvec4
+ * uvec
+ * uvec2
+ * uvec3
+ * uvec4
+ * ivec
+ * ivec2
+ * ivec3
+ * ivec4
+
+### Available Require Features
+ * robustBufferAccess
+ * fullDrawIndexUint32
+ * imageCubeArray
+ * independentBlend
+ * geometryShader
+ * tessellationShader
+ * sampleRateShading
+ * dualSrcBlend
+ * logicOp
+ * multiDrawIndirect
+ * drawIndirectFirstInstance
+ * depthClamp
+ * depthBiasClamp
+ * fillModeNonSolid
+ * depthBounds
+ * wideLines
+ * largePoints
+ * alphaToOne
+ * multiViewport
+ * samplerAnisotropy
+ * textureCompressionETC2
+ * textureCompressionASTC_LDR
+ * textureCompressionBC
+ * occlusionQueryPrecise
+ * pipelineStatisticsQuery
+ * vertexPipelineStoresAndAtomics
+ * fragmentStoresAndAtomics
+ * shaderTessellationAndGeometryPointSize
+ * shaderImageGatherExtended
+ * shaderStorageImageExtendedFormats
+ * shaderStorageImageMultisample
+ * shaderStorageImageReadWithoutFormat
+ * shaderStorageImageWriteWithoutFormat
+ * shaderUniformBufferArrayDynamicIndexing
+ * shaderSampledImageArrayDynamicIndexing
+ * shaderStorageBufferArrayDynamicIndexing
+ * shaderStorageImageArrayDynamicIndexing
+ * shaderClipDistance
+ * shaderCullDistance
+ * shaderFloat64
+ * shaderInt64
+ * shaderInt16
+ * shaderResourceResidency
+ * shaderResourceMinLod
+ * sparseBinding
+ * sparseResidencyBuffer
+ * sparseResidencyImage2D
+ * sparseResidencyImage3D
+ * sparseResidency2Samples
+ * sparseResidency4Samples
+ * sparseResidency8Samples
+ * sparseResidency16Samples
+ * sparseResidencyAliased
+ * variableMultisampleRate
+ * inheritedQueries
+
+### Image Formats
+ * A1R5G5B5_UNORM_PACK16
+ * A2B10G10R10_SINT_PACK32
+ * A2B10G10R10_SNORM_PACK32
+ * A2B10G10R10_SSCALED_PACK32
+ * A2B10G10R10_UINT_PACK32
+ * A2B10G10R10_UNORM_PACK32
+ * A2B10G10R10_USCALED_PACK32
+ * A2R10G10B10_SINT_PACK32
+ * A2R10G10B10_SNORM_PACK32
+ * A2R10G10B10_SSCALED_PACK32
+ * A2R10G10B10_UINT_PACK32
+ * A2R10G10B10_UNORM_PACK32
+ * A2R10G10B10_USCALED_PACK32
+ * A8B8G8R8_SINT_PACK32
+ * A8B8G8R8_SNORM_PACK32
+ * A8B8G8R8_SRGB_PACK32
+ * A8B8G8R8_SSCALED_PACK32
+ * A8B8G8R8_UINT_PACK32
+ * A8B8G8R8_UNORM_PACK32
+ * A8B8G8R8_USCALED_PACK32
+ * B10G11R11_UFLOAT_PACK32
+ * B4G4R4A4_UNORM_PACK16
+ * B5G5R5A1_UNORM_PACK16
+ * B5G6R5_UNORM_PACK16
+ * B8G8R8A8_SINT
+ * B8G8R8A8_SNORM
+ * B8G8R8A8_SRGB
+ * B8G8R8A8_SSCALED
+ * B8G8R8A8_UINT
+ * B8G8R8A8_UNORM
+ * B8G8R8A8_USCALED
+ * B8G8R8_SINT
+ * B8G8R8_SNORM
+ * B8G8R8_SRGB
+ * B8G8R8_SSCALED
+ * B8G8R8_UINT
+ * B8G8R8_UNORM
+ * B8G8R8_USCALED
+ * D16_UNORM
+ * D16_UNORM_S8_UINT
+ * D24_UNORM_S8_UINT
+ * D32_SFLOAT
+ * D32_SFLOAT_S8_UINT
+ * R16G16B16A16_SFLOAT
+ * R16G16B16A16_SINT
+ * R16G16B16A16_SNORM
+ * R16G16B16A16_SSCALED
+ * R16G16B16A16_UINT
+ * R16G16B16A16_UNORM
+ * R16G16B16A16_USCALED
+ * R16G16B16_SFLOAT
+ * R16G16B16_SINT
+ * R16G16B16_SNORM
+ * R16G16B16_SSCALED
+ * R16G16B16_UINT
+ * R16G16B16_UNORM
+ * R16G16B16_USCALED
+ * R16G16_SFLOAT
+ * R16G16_SINT
+ * R16G16_SNORM
+ * R16G16_SSCALED
+ * R16G16_UINT
+ * R16G16_UNORM
+ * R16G16_USCALED
+ * R16_SFLOAT
+ * R16_SINT
+ * R16_SNORM
+ * R16_SSCALED
+ * R16_UINT
+ * R16_UNORM
+ * R16_USCALED
+ * R32G32B32A32_SFLOAT
+ * R32G32B32A32_SINT
+ * R32G32B32A32_UINT
+ * R32G32B32_SFLOAT
+ * R32G32B32_SINT
+ * R32G32B32_UINT
+ * R32G32_SFLOAT
+ * R32G32_SINT
+ * R32G32_UINT
+ * R32_SFLOAT
+ * R32_SINT
+ * R32_UINT
+ * R4G4B4A4_UNORM_PACK16
+ * R4G4_UNORM_PACK8
+ * R5G5B5A1_UNORM_PACK16
+ * R5G6B5_UNORM_PACK16
+ * R64G64B64A64_SFLOAT
+ * R64G64B64A64_SINT
+ * R64G64B64A64_UINT
+ * R64G64B64_SFLOAT
+ * R64G64B64_SINT
+ * R64G64B64_UINT
+ * R64G64_SFLOAT
+ * R64G64_SINT
+ * R64G64_UINT
+ * R64_SFLOAT
+ * R64_SINT
+ * R64_UINT
+ * R8G8B8A8_SINT
+ * R8G8B8A8_SNORM
+ * R8G8B8A8_SRGB
+ * R8G8B8A8_SSCALED
+ * R8G8B8A8_UINT
+ * R8G8B8A8_UNORM
+ * R8G8B8A8_USCALED
+ * R8G8B8_SINT
+ * R8G8B8_SNORM
+ * R8G8B8_SRGB
+ * R8G8B8_SSCALED
+ * R8G8B8_UINT
+ * R8G8B8_UNORM
+ * R8G8B8_USCALED
+ * R8G8_SINT
+ * R8G8_SNORM
+ * R8G8_SRGB
+ * R8G8_SSCALED
+ * R8G8_UINT
+ * R8G8_UNORM
+ * R8G8_USCALED
+ * R8_SINT
+ * R8_SNORM
+ * R8_SRGB
+ * R8_SSCALED
+ * R8_UINT
+ * R8_UNORM
+ * R8_USCALED
+ * S8_UINT
+ * X8_D24_UNORM_PACK32
+
+1- https://github.com/Igalia/vkrunner/blob/d817f8b186cccebed89471580a685dc80a330946/README.md
diff --git a/docs/vulkan_resource_and_descriptor.md b/docs/vulkan_resource_and_descriptor.md
new file mode 100644
index 0000000..a8439da
--- /dev/null
+++ b/docs/vulkan_resource_and_descriptor.md
@@ -0,0 +1,66 @@
+# Classes for Vulkan resources and descriptors
+ * DRAFT
+
+Vulkan has many resource and descriptor types.
+Since it is complicated to manage them e.g.,
+create/allocate/map/read/write/destory, we create several classes to
+provide an abstraction. This document briefly explains those classes.
+
+
+### Resource class
+Represents a main resource i.e., VkBuffer or VkImage (See
+[Resources in Vulkan spec](
+https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#resources))
+in GPU device and an additional VkBuffer to allow read/write to the
+main resource from CPU.
+
+If the main resource is accessible from CPU, the additional
+VkBuffer is not needed and it will be `VK_NULL_HANDLE`.
+Otherwise, the additional VkBuffer has the same size with the main
+resource and we must copy the main resource to the VkBuffer or
+copy the VkBuffer to the main resource when reading from/write to
+the main resource.
+
+The Resource class has Buffer and Image sub-classes.
+
+#### Buffer class
+Abstracts VkBuffer and creates/allocates/maps/destorys
+VkBuffer and VkBufferView resources.
+
+#### Image class
+Abstracts VkImage and creates/allocates/maps/destorys
+VkImage and VkImageView resources.
+
+
+### Sampler class
+ * TODO: Not implementated yet
+ * Represent [VkSampler](
+ https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#samplers)
+
+
+### Descriptor class
+Represents [Descriptor Types](
+https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#descriptorsets-types).
+There are 11 Descriptor Types and they need different resources.
+For example, a Combined Image Sampler needs both Image and
+Sampler objects while Storage Buffer needs only a Buffer object.
+
+* TODO: Describe 11 sub-classes of Descriptor for those Descriptor Types.
+
+
+### FrameBuffer class
+Abstracts [VkFrameBuffer](
+https://www.khronos.org/registry/vulkan/specs/1.1/html/vkspec.html#_framebuffers)
+for attachments and an Image for the FrameBuffer.
+The usage of the Image is `VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT`.
+
+
+### VertexBuffer class
+Manages vertices data and a Buffer whose usage is
+`VK_BUFFER_USAGE_VERTEX_BUFFER_BIT`.
+
+
+### IndexBuffer class
+ * TODO: Not implementated yet
+ * Manages indices data and a Buffer whose usage is
+ `VK_BUFFER_USAGE_INDEX_BUFFER_BIT`.
diff --git a/include/amber/amber.h b/include/amber/amber.h
new file mode 100644
index 0000000..f723c1b
--- /dev/null
+++ b/include/amber/amber.h
@@ -0,0 +1,44 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef AMBER_AMBER_H_
+#define AMBER_AMBER_H_
+
+#include "amber/result.h"
+
+#include <string>
+
+namespace amber {
+
+enum class EngineType : uint8_t {
+ kVulkan = 0,
+};
+
+struct Options {
+ EngineType engine = EngineType::kVulkan;
+ void* default_device = nullptr;
+ bool parse_only = false;
+};
+
+class Amber {
+ public:
+ Amber();
+ ~Amber();
+
+ amber::Result Execute(const std::string& data, const Options& opts);
+};
+
+} // namespace amber
+
+#endif // AMBER_AMBER_H_
diff --git a/include/amber/result.h b/include/amber/result.h
new file mode 100644
index 0000000..ec57289
--- /dev/null
+++ b/include/amber/result.h
@@ -0,0 +1,50 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef AMBER_RESULT_H_
+#define AMBER_RESULT_H_
+
+#include <string>
+#include <vector>
+
+namespace amber {
+
+class Result {
+ public:
+ Result();
+ Result(const std::string& err);
+ Result(const Result&);
+ ~Result();
+
+ Result& operator=(const Result&);
+
+ bool IsSuccess() const { return succeeded_; }
+ const std::string& Error() const { return error_; }
+
+ void SetImageData(const std::vector<uint8_t>& data) { image_data_ = data; }
+ const std::vector<uint8_t>& ImageData() const { return image_data_; }
+
+ void SetBufferData(const std::vector<uint8_t>& data) { buffer_data_ = data; }
+ const std::vector<uint8_t>& BufferData() const { return buffer_data_; }
+
+ private:
+ bool succeeded_;
+ std::string error_;
+ std::vector<uint8_t> image_data_;
+ std::vector<uint8_t> buffer_data_;
+};
+
+} // namespace amber
+
+#endif // AMBER_RESULT_H_
diff --git a/samples/CMakeLists.txt b/samples/CMakeLists.txt
new file mode 100644
index 0000000..c5142c5
--- /dev/null
+++ b/samples/CMakeLists.txt
@@ -0,0 +1,40 @@
+# Copyright 2018 The Amber 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.
+
+include_directories("${PROJECT_SOURCE_DIR}/include")
+
+set(AMBER_SOURCES
+ amber.cc
+ ${CMAKE_BINARY_DIR}/src/build-versions.h
+)
+
+add_executable(amber ${AMBER_SOURCES})
+target_include_directories(amber PRIVATE "${CMAKE_CURRENT_BINARY_DIR}/..")
+set_target_properties(amber PROPERTIES OUTPUT_NAME "amber")
+target_link_libraries(amber libamber)
+
+add_custom_command(
+ OUTPUT ${CMAKE_BINARY_DIR}/src/build-versions.h
+ COMMAND
+ ${PYTHON_EXE}
+ ${CMAKE_CURRENT_SOURCE_DIR}/../tools/update_build_version.py
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${spirv-tools_SOURCE_DIR}
+ ${CMAKE_CURRENT_SOURCE_DIR}/../third_party/spirv-headers
+ ${glslang_SOURCE_DIR}
+ ${shaderc_SOURCE_DIR}
+ WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/.."
+ COMMENT "Update build-versions.h in the build directory"
+)
diff --git a/samples/amber.cc b/samples/amber.cc
new file mode 100644
index 0000000..9d76f5f
--- /dev/null
+++ b/samples/amber.cc
@@ -0,0 +1,177 @@
+// Copyright 2018 The Amber 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.
+
+#include "amber/amber.h"
+
+#include <cassert>
+#include <cstdlib>
+#include <iostream>
+#include <vector>
+
+#include "src/build-versions.h"
+
+namespace {
+
+struct Options {
+ std::string input_filename;
+
+ std::string image_filename;
+ std::string buffer_filename;
+ long buffer_binding_index = 0;
+ bool parse_only = false;
+ bool show_help = false;
+ bool show_version_info = false;
+};
+
+const char kUsage[] = R"(Usage: amber [options] SCRIPT
+
+ options:
+ -p -- Parse input files only; Don't execute
+ -i <filename> -- Write rendering to <filename> as a PPM image.
+ -b <filename> -- Write contents of a UBO or SSBO to <filename>.
+ -B <buffer> -- Index of buffer to write. Defaults buffer 0.
+ -V, --version -- Output version information for Amber and libraries.
+ -h -- This help text.
+)";
+
+bool ParseArgs(const std::vector<std::string>& args, Options* opts) {
+ for (size_t i = 1; i < args.size(); ++i) {
+ const std::string& arg = args[i];
+ if (arg == "-i") {
+ ++i;
+ if (i >= args.size()) {
+ std::cerr << "Missing value for -i argument." << std::endl;
+ return false;
+ }
+ opts->image_filename = args[i];
+
+ } else if (arg == "-b") {
+ ++i;
+ if (i >= args.size()) {
+ std::cerr << "Missing value for -b argument." << std::endl;
+ return false;
+ }
+ opts->buffer_filename = args[i];
+
+ } else if (arg == "-B") {
+ ++i;
+ if (i >= args.size()) {
+ std::cerr << "Missing value for -B argument." << std::endl;
+ return false;
+ }
+ opts->buffer_binding_index = strtol(args[i].c_str(), nullptr, 10);
+
+ if (opts->buffer_binding_index < 0) {
+ std::cerr << "Invalid value for -B, must be 0 or greater." << std::endl;
+ return false;
+ }
+
+ } else if (arg == "-h" || arg == "--help") {
+ opts->show_help = true;
+ } else if (arg == "-V" || arg == "--version") {
+ opts->show_version_info = true;
+ } else if (arg == "-p") {
+ opts->parse_only = true;
+ } else {
+ opts->input_filename = args[i];
+ }
+ }
+
+ return true;
+}
+
+std::string ReadFile(const std::string& input_file) {
+ FILE* file = fopen(input_file.c_str(), "rb");
+ if (!file) {
+ std::cerr << "Failed to open " << input_file << std::endl;
+ return {};
+ }
+
+ fseek(file, 0, SEEK_END);
+ long tell_file_size = ftell(file);
+ if (tell_file_size <= 0) {
+ std::cerr << "Input file of incorrect size: " << input_file << std::endl;
+ return {};
+ }
+ fseek(file, 0, SEEK_SET);
+
+ size_t file_size = static_cast<size_t>(tell_file_size);
+
+ std::vector<char> data;
+ data.resize(file_size);
+
+ size_t bytes_read = fread(data.data(), sizeof(char), file_size, file);
+ fclose(file);
+ if (bytes_read != file_size) {
+ std::cerr << "Failed to read " << input_file << std::endl;
+ return {};
+ }
+
+ return std::string(data.begin(), data.end());
+}
+
+} // namespace
+
+int main(int argc, const char** argv) {
+ std::vector<std::string> args(argv, argv + argc);
+ Options options;
+
+ if (!ParseArgs(args, &options)) {
+ std::cerr << "Failed to parse arguments." << std::endl;
+ return 1;
+ }
+
+ if (options.show_version_info) {
+ std::cout << "Amber : " << AMBER_VERSION << std::endl;
+ std::cout << "SPIRV-Tools : " << SPIRV_TOOLS_VERSION << std::endl;
+ std::cout << "SPIRV-Headers: " << SPIRV_HEADERS_VERSION << std::endl;
+ std::cout << "GLSLang : " << GLSLANG_VERSION << std::endl;
+ std::cout << "Shaderc : " << SHADERC_VERSION << std::endl;
+ }
+
+ if (options.show_help) {
+ std::cout << kUsage << std::endl;
+ return 0;
+ }
+
+ if (options.input_filename.empty()) {
+ std::cerr << "Input file must be provided." << std::endl;
+ return 2;
+ }
+
+ auto data = ReadFile(options.input_filename);
+ if (data.empty())
+ return 1;
+
+ amber::Amber vk;
+ amber::Options amber_options;
+ amber_options.parse_only = options.parse_only;
+ amber::Result result = vk.Execute(data, amber_options);
+ if (!result.IsSuccess()) {
+ std::cerr << result.Error() << std::endl;
+ return 1;
+ }
+
+ if (!options.buffer_filename.empty()) {
+ // TOOD(dsinclair): Write buffer file
+ assert(false);
+ }
+
+ if (!options.image_filename.empty()) {
+ // TODO(dsinclair): Write image file
+ assert(false);
+ }
+
+ return 0;
+}
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..50531e8
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,84 @@
+# Copyright 2018 The Amber 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.
+
+set(AMBER_SOURCES
+ amber.cc
+ amber_impl.cc
+ amberscript/executor.cc
+ amberscript/parser.cc
+ amberscript/pipeline.cc
+ amberscript/script.cc
+ amberscript/shader.cc
+ command.cc
+ command_data.cc
+ datum_type.cc
+ engine.cc
+ executor.cc
+ format.cc
+ parser.cc
+ pipeline_data.cc
+ result.cc
+ script.cc
+ shader_compiler.cc
+ tokenizer.cc
+ value.cc
+ vkscript/command_parser.cc
+ vkscript/datum_type_parser.cc
+ vkscript/executor.cc
+ vkscript/format_parser.cc
+ vkscript/nodes.cc
+ vkscript/parser.cc
+ vkscript/script.cc
+ vkscript/section_parser.cc
+)
+
+add_library(libamber ${AMBER_SOURCES})
+amber_default_compile_options(libamber)
+set_target_properties(libamber PROPERTIES OUTPUT_NAME "amber")
+# TODO(dsinclair): Remove pthread when building on windows.
+target_link_libraries(libamber SPIRV-Tools shaderc SPIRV pthread)
+
+if (${Vulkan_FOUND})
+ target_link_libraries(libamber libamberenginevulkan)
+ add_subdirectory(vulkan)
+endif()
+
+set(TEST_SRCS
+ amberscript/parser_test.cc
+ amberscript/pipeline_test.cc
+ command_data_test.cc
+ result_test.cc
+ shader_compiler_test.cc
+ tokenizer_test.cc
+ vkscript/command_parser_test.cc
+ vkscript/datum_type_parser_test.cc
+ vkscript/executor_test.cc
+ vkscript/format_parser_test.cc
+ vkscript/parser_test.cc
+ vkscript/section_parser_test.cc
+)
+
+if (${Vulkan_FOUND})
+ list(APPEND TEST_SRCS vulkan/bit_copy_test.cc)
+endif()
+
+add_executable(amber_unittests ${TEST_SRCS})
+target_compile_options(amber_unittests PRIVATE
+ -Wno-global-constructors)
+
+target_include_directories(amber_unittests PRIVATE
+ ${gtest_SOURCE_DIR}/include)
+target_link_libraries(amber_unittests libamber gtest_main)
+amber_default_compile_options(amber_unittests)
+add_test(NAME amber_unittests COMMAND amber_unittests)
diff --git a/src/amber.cc b/src/amber.cc
new file mode 100644
index 0000000..dda019e
--- /dev/null
+++ b/src/amber.cc
@@ -0,0 +1,30 @@
+// Copyright 2018 The Amber 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.
+
+#include "amber/amber.h"
+
+#include "src/amber_impl.h"
+
+namespace amber {
+
+Amber::Amber() = default;
+
+Amber::~Amber() = default;
+
+amber::Result Amber::Execute(const std::string& input, const Options& opts) {
+ AmberImpl impl;
+ return impl.Execute(input, opts);
+}
+
+} // namespace amber
diff --git a/src/amber_impl.cc b/src/amber_impl.cc
new file mode 100644
index 0000000..93e61a7
--- /dev/null
+++ b/src/amber_impl.cc
@@ -0,0 +1,70 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amber_impl.h"
+
+#include "src/amberscript/executor.h"
+#include "src/amberscript/parser.h"
+#include "src/engine.h"
+#include "src/executor.h"
+#include "src/make_unique.h"
+#include "src/parser.h"
+#include "src/vkscript/executor.h"
+#include "src/vkscript/parser.h"
+
+namespace amber {
+
+AmberImpl::AmberImpl() = default;
+
+AmberImpl::~AmberImpl() = default;
+
+amber::Result AmberImpl::Execute(const std::string& input,
+ const Options& opts) {
+ std::unique_ptr<Parser> parser;
+ std::unique_ptr<Executor> executor;
+ if (input.substr(0, 7) == "#!amber") {
+ parser = MakeUnique<amberscript::Parser>();
+ executor = MakeUnique<amberscript::Executor>();
+ } else {
+ parser = MakeUnique<vkscript::Parser>();
+ executor = MakeUnique<vkscript::Executor>();
+ }
+
+ Result r = parser->Parse(input);
+ if (!r.IsSuccess())
+ return r;
+
+ if (opts.parse_only)
+ return {};
+
+ auto engine = Engine::Create(opts.engine);
+ if (!engine)
+ return Result("Failed to create engine");
+
+ if (opts.default_device)
+ r = engine->InitializeWithDevice(opts.default_device);
+ else
+ r = engine->Initialize();
+
+ if (!r.IsSuccess())
+ return r;
+
+ r = executor->Execute(engine.get(), parser->GetScript());
+ if (!r.IsSuccess())
+ return r;
+
+ return engine->Shutdown();
+}
+
+} // namespace amber
diff --git a/src/amber_impl.h b/src/amber_impl.h
new file mode 100644
index 0000000..5b962a8
--- /dev/null
+++ b/src/amber_impl.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_AMBER_IMPL_H_
+#define SRC_AMBER_IMPL_H_
+
+#include "amber/amber.h"
+#include "amber/result.h"
+
+namespace amber {
+
+class AmberImpl {
+ public:
+ AmberImpl();
+ ~AmberImpl();
+
+ Result Execute(const std::string& data, const Options& opts);
+};
+
+} // namespace amber
+
+#endif // SRC_AMBER_IMPL_H_
diff --git a/src/amberscript/executor.cc b/src/amberscript/executor.cc
new file mode 100644
index 0000000..dba14e3
--- /dev/null
+++ b/src/amberscript/executor.cc
@@ -0,0 +1,34 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amberscript/executor.h"
+
+namespace amber {
+namespace amberscript {
+
+Executor::Executor() : amber::Executor() {}
+
+Executor::~Executor() = default;
+
+Result Executor::Execute(Engine*, const amber::Script* src_script) {
+ if (!src_script->IsAmberScript())
+ return Result("AmberScript executor called with non-amber script source");
+
+ // const amberscript::Script* script = ToAmberScript(src_script);
+
+ return {};
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/amberscript/executor.h b/src/amberscript/executor.h
new file mode 100644
index 0000000..27be740
--- /dev/null
+++ b/src/amberscript/executor.h
@@ -0,0 +1,37 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_AMBERSCRIPT_EXECUTOR_H_
+#define SRC_AMBERSCRIPT_EXECUTOR_H_
+
+#include "amber/result.h"
+#include "src/engine.h"
+#include "src/executor.h"
+#include "src/script.h"
+
+namespace amber {
+namespace amberscript {
+
+class Executor : public amber::Executor {
+ public:
+ Executor();
+ ~Executor() override;
+
+ Result Execute(Engine*, const amber::Script*) override;
+};
+
+} // namespace amberscript
+} // namespace amber
+
+#endif // SRC_AMBERSCRIPT_EXECUTOR_H_
diff --git a/src/amberscript/parser.cc b/src/amberscript/parser.cc
new file mode 100644
index 0000000..6535520
--- /dev/null
+++ b/src/amberscript/parser.cc
@@ -0,0 +1,309 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amberscript/parser.h"
+
+#include <cassert>
+
+#include "src/make_unique.h"
+#include "src/tokenizer.h"
+
+namespace amber {
+namespace amberscript {
+
+Parser::Parser() : amber::Parser() {}
+
+Parser::~Parser() = default;
+
+std::string Parser::make_error(const std::string& err) {
+ return std::to_string(tokenizer_->GetCurrentLine()) + ": " + err;
+}
+
+Result Parser::Parse(const std::string& data) {
+ tokenizer_ = MakeUnique<Tokenizer>(data);
+
+ for (auto token = tokenizer_->NextToken(); !token->IsEOS();
+ token = tokenizer_->NextToken()) {
+ if (token->IsEOL())
+ continue;
+ if (!token->IsString())
+ return Result(make_error("expected string"));
+
+ Result r;
+ std::string tok = token->AsString();
+ if (tok == "SHADER") {
+ r = ParseShaderBlock();
+ } else if (tok == "PIPELINE") {
+ r = ParsePipelineBlock();
+ } else {
+ r = Result("unknown token: " + tok);
+ }
+ if (!r.IsSuccess())
+ return Result(make_error(r.Error()));
+ }
+ return {};
+}
+
+Result Parser::ToShaderType(const std::string& str, ShaderType* type) {
+ assert(type);
+
+ if (str == "vertex")
+ *type = ShaderType::kVertex;
+ else if (str == "fragment")
+ *type = ShaderType::kFragment;
+ else if (str == "geometry")
+ *type = ShaderType::kGeometry;
+ else if (str == "tessellation_evaluation")
+ *type = ShaderType::kTessellationEvaluation;
+ else if (str == "tessellation_control")
+ *type = ShaderType::kTessellationControl;
+ else if (str == "compute")
+ *type = ShaderType::kCompute;
+ else
+ return Result("unknown shader type: " + str);
+ return {};
+}
+
+Result Parser::ToShaderFormat(const std::string& str, ShaderFormat* fmt) {
+ assert(fmt);
+
+ if (str == "GLSL")
+ *fmt = ShaderFormat::kGlsl;
+ else if (str == "SPIRV-ASM")
+ *fmt = ShaderFormat::kSpirvAsm;
+ else if (str == "SPIRV-HEX")
+ *fmt = ShaderFormat::kSpirvHex;
+ else
+ return Result("unknown shader format: " + str);
+ return {};
+}
+
+Result Parser::ToPipelineType(const std::string& str, PipelineType* type) {
+ assert(type);
+
+ if (str == "compute")
+ *type = PipelineType::kCompute;
+ else if (str == "graphics")
+ *type = PipelineType::kGraphics;
+ else
+ return Result("unknown pipeline type: " + str);
+ return {};
+}
+
+Result Parser::ValidateEndOfStatement(const std::string& name) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return {};
+ return Result("extra parameters after " + name);
+}
+
+Result Parser::ParseShaderBlock() {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid token when looking for shader type");
+
+ ShaderType type = ShaderType::kVertex;
+ Result r = ToShaderType(token->AsString(), &type);
+ if (!r.IsSuccess())
+ return r;
+
+ auto shader = MakeUnique<Shader>(type);
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid token when looking for shader name");
+
+ shader->SetName(token->AsString());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid token when looking for shader format");
+
+ std::string fmt = token->AsString();
+ if (fmt == "PASSTHROUGH") {
+ if (type != ShaderType::kVertex) {
+ return Result(
+ "invalid shader type for PASSTHROUGH. Only vertex "
+ "PASSTHROUGH allowed");
+ }
+ shader->SetFormat(ShaderFormat::kSpirvAsm);
+ shader->SetData(kPassThroughShader);
+
+ r = script_.AddShader(std::move(shader));
+ if (!r.IsSuccess())
+ return r;
+
+ return ValidateEndOfStatement("SHADER PASSTHROUGH");
+ }
+
+ ShaderFormat format = ShaderFormat::kGlsl;
+ r = ToShaderFormat(fmt, &format);
+ if (!r.IsSuccess())
+ return r;
+
+ shader->SetFormat(format);
+
+ r = ValidateEndOfStatement("SHADER command");
+ if (!r.IsSuccess())
+ return r;
+
+ std::string data = tokenizer_->ExtractToNext("END");
+ if (data.empty())
+ return Result("SHADER must not be empty");
+
+ shader->SetData(data);
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString() || token->AsString() != "END")
+ return Result("SHADER missing END command");
+
+ r = script_.AddShader(std::move(shader));
+ if (!r.IsSuccess())
+ return r;
+
+ return ValidateEndOfStatement("END");
+}
+
+Result Parser::ParsePipelineBlock() {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid token when looking for pipeline type");
+
+ PipelineType type = PipelineType::kCompute;
+ Result r = ToPipelineType(token->AsString(), &type);
+ if (!r.IsSuccess())
+ return r;
+
+ auto pipeline = MakeUnique<Pipeline>(type);
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid token when looking for pipeline name");
+
+ pipeline->SetName(token->AsString());
+
+ r = ValidateEndOfStatement("PIPELINE command");
+ if (!r.IsSuccess())
+ return r;
+
+ for (token = tokenizer_->NextToken(); !token->IsEOS();
+ token = tokenizer_->NextToken()) {
+ if (token->IsEOL())
+ continue;
+ if (!token->IsString())
+ return Result("expected string");
+
+ std::string tok = token->AsString();
+ if (tok == "END") {
+ break;
+ } else if (tok == "ATTACH") {
+ r = ParsePipelineAttach(pipeline.get());
+ } else if (tok == "ENTRY_POINT") {
+ r = ParsePipelineEntryPoint(pipeline.get());
+ } else if (tok == "SHADER_OPTIMIZATION") {
+ r = ParsePipelineShaderOptimizations(pipeline.get());
+ } else {
+ r = Result("unknown token in pipeline block: " + tok);
+ }
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ if (!token->IsString() || token->AsString() != "END")
+ return Result("PIPELINE missing END command");
+
+ r = pipeline->Validate();
+ if (!r.IsSuccess())
+ return r;
+
+ r = script_.AddPipeline(std::move(pipeline));
+ if (!r.IsSuccess())
+ return r;
+
+ return ValidateEndOfStatement("END");
+}
+
+Result Parser::ParsePipelineAttach(Pipeline* pipeline) {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid token in ATTACH command");
+
+ auto* shader = script_.GetShader(token->AsString());
+ if (!shader)
+ return Result("unknown shader in ATTACH command");
+
+ Result r = pipeline->AddShader(shader);
+ if (!r.IsSuccess())
+ return r;
+
+ return ValidateEndOfStatement("ATTACH command");
+}
+
+Result Parser::ParsePipelineEntryPoint(Pipeline* pipeline) {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("missing shader name in ENTRY_POINT command");
+
+ auto* shader = script_.GetShader(token->AsString());
+ if (!shader)
+ return Result("unknown shader in ENTRY_POINT command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("invalid value in ENTRY_POINT command");
+
+ Result r = pipeline->SetShaderEntryPoint(shader, token->AsString());
+ if (!r.IsSuccess())
+ return r;
+
+ return ValidateEndOfStatement("ENTRY_POINT command");
+}
+
+Result Parser::ParsePipelineShaderOptimizations(Pipeline* pipeline) {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("missing shader name in SHADER_OPTIMIZATION command");
+
+ auto* shader = script_.GetShader(token->AsString());
+ if (!shader)
+ return Result("unknown shader in SHADER_OPTIMIZATION command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOL())
+ return Result("extra parameters after SHADER_OPTIMIZATION command");
+
+ std::vector<std::string> optimizations;
+ while (true) {
+ token = tokenizer_->NextToken();
+ if (token->IsEOL())
+ continue;
+ if (token->IsEOS())
+ return Result("SHADER_OPTIMIZATION missing END command");
+ if (!token->IsString())
+ return Result("SHADER_OPTIMIZATION options must be strings");
+ if (token->AsString() == "END")
+ break;
+
+ optimizations.push_back(token->AsString());
+ }
+
+ Result r = pipeline->SetShaderOptimizations(shader, optimizations);
+ if (!r.IsSuccess())
+ return r;
+
+ return ValidateEndOfStatement("SHADER_OPTIMIZATION command");
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/amberscript/parser.h b/src/amberscript/parser.h
new file mode 100644
index 0000000..5239c0f
--- /dev/null
+++ b/src/amberscript/parser.h
@@ -0,0 +1,61 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_AMBERSCRIPT_PARSER_H_
+#define SRC_AMBERSCRIPT_PARSER_H_
+
+#include <memory>
+#include <string>
+
+#include "amber/result.h"
+#include "src/amberscript/script.h"
+#include "src/parser.h"
+#include "src/script.h"
+
+namespace amber {
+
+class Tokenizer;
+
+namespace amberscript {
+
+class Parser : public amber::Parser {
+ public:
+ Parser();
+ ~Parser() override;
+
+ // amber::Parser
+ Result Parse(const std::string& data) override;
+ const amber::Script* GetScript() const override { return &script_; }
+
+ private:
+ std::string make_error(const std::string& err);
+ Result ToShaderType(const std::string& str, ShaderType* type);
+ Result ToShaderFormat(const std::string& str, ShaderFormat* fmt);
+ Result ToPipelineType(const std::string& str, PipelineType* type);
+ Result ValidateEndOfStatement(const std::string& name);
+
+ Result ParseShaderBlock();
+ Result ParsePipelineBlock();
+ Result ParsePipelineAttach(Pipeline*);
+ Result ParsePipelineEntryPoint(Pipeline*);
+ Result ParsePipelineShaderOptimizations(Pipeline*);
+
+ amberscript::Script script_;
+ std::unique_ptr<Tokenizer> tokenizer_;
+};
+
+} // namespace amberscript
+} // namespace amber
+
+#endif // SRC_AMBERSCRIPT_PARSER_H_
diff --git a/src/amberscript/parser_test.cc b/src/amberscript/parser_test.cc
new file mode 100644
index 0000000..fafb261
--- /dev/null
+++ b/src/amberscript/parser_test.cc
@@ -0,0 +1,912 @@
+// Copyright 2018 The Amber 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 parseried.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/amberscript/parser.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+namespace amberscript {
+namespace {
+
+struct NameData {
+ const char* name;
+};
+
+struct ShaderTypeData {
+ const char* name;
+ ShaderType type;
+};
+
+struct ShaderFormatData {
+ const char* name;
+ ShaderFormat format;
+};
+
+} // namespace
+
+using AmberScriptParserTest = testing::Test;
+
+TEST_F(AmberScriptParserTest, EmptyInput) {
+ std::string in = "";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = parser.GetScript();
+ ASSERT_TRUE(script->IsAmberScript());
+}
+
+TEST_F(AmberScriptParserTest, InvalidStartToken) {
+ std::string in = R"(#!amber
+# Start comment
+1234)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: expected string", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, UnknownStartToken) {
+ std::string in = "INVALID token";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: unknown token: INVALID", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderPassThrough) {
+ std::string in = "SHADER vertex my_shader1 PASSTHROUGH";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ const auto& shaders = script->GetShaders();
+ ASSERT_EQ(1U, shaders.size());
+
+ const auto* shader = shaders[0].get();
+ EXPECT_EQ("my_shader1", shader->GetName());
+ EXPECT_EQ(ShaderType::kVertex, shader->GetType());
+ EXPECT_EQ(ShaderFormat::kSpirvAsm, shader->GetFormat());
+ EXPECT_EQ(kPassThroughShader, shader->GetData());
+}
+
+TEST_F(AmberScriptParserTest, ShaderInvalidShaderTypeToken) {
+ std::string in = "SHADER 1234 my_shader PASSTHROUGH";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: invalid token when looking for shader type", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderInvalidShaderNameToken) {
+ std::string in = "SHADER vertex 12345 PASSTHROUGH";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: invalid token when looking for shader name", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderInvalidShaderFormatToken) {
+ std::string in = "SHADER vertex my_shader 1234";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: invalid token when looking for shader format", r.Error());
+}
+
+using AmberScriptParserShaderPassThroughTest = testing::TestWithParam<NameData>;
+TEST_P(AmberScriptParserShaderPassThroughTest, ShaderPassThroughWithoutVertex) {
+ auto test_data = GetParam();
+
+ std::string in =
+ "SHADER " + std::string(test_data.name) + " my_shader PASSTHROUGH";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(
+ "1: invalid shader type for PASSTHROUGH. Only vertex PASSTHROUGH "
+ "allowed",
+ r.Error());
+}
+INSTANTIATE_TEST_CASE_P(AmberScriptParserShaderPassThroughTests,
+ AmberScriptParserShaderPassThroughTest,
+ testing::Values(NameData{"fragment"},
+ NameData{"geometry"},
+ NameData{"tessellation_evaluation"},
+ NameData{"tessellation_control"},
+ NameData{"compute"}), );
+
+TEST_F(AmberScriptParserTest, ShaderPassThroughUnknownShaderType) {
+ std::string in = "SHADER UNKNOWN my_shader PASSTHROUGH";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: unknown shader type: UNKNOWN", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderPassThroughMissingName) {
+ std::string in = "SHADER vertex PASSTHROUGH";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: invalid token when looking for shader format", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderPassThroughExtraParameters) {
+ std::string in = "SHADER vertex my_shader PASSTHROUGH INVALID";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: extra parameters after SHADER PASSTHROUGH", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, Shader) {
+ std::string shader_result = R"(
+# Shader has a comment in it.
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+)";
+
+ std::string in = R"(#!amber
+SHADER geometry shader_name GLSL
+)" + shader_result +
+ "END";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ const auto& shaders = script->GetShaders();
+ ASSERT_EQ(1U, shaders.size());
+
+ const auto* shader = shaders[0].get();
+ EXPECT_EQ("shader_name", shader->GetName());
+ EXPECT_EQ(ShaderType::kGeometry, shader->GetType());
+ EXPECT_EQ(ShaderFormat::kGlsl, shader->GetFormat());
+ EXPECT_EQ(shader_result, shader->GetData());
+}
+
+TEST_F(AmberScriptParserTest, ShaderInvalidFormat) {
+ std::string in = R"(#!amber
+SHADER geometry shader_name INVALID
+# Shader has a comment in it.
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: unknown shader format: INVALID", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderMissingFormat) {
+ std::string in = R"(#!amber
+SHADER geometry shader_name
+# Shader has a comment in it.
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid token when looking for shader format", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderEmpty) {
+ std::string in = R"(#!amber
+SHADER geometry shader_name GLSL
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: SHADER must not be empty", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderMissingName) {
+ std::string in = R"(#!amber
+SHADER geometry GLSL
+# Shader has a comment in it.
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: invalid token when looking for shader format", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderMissingEnd) {
+ std::string in = R"(#!amber
+SHADER geometry shader_name GLSL
+# Shader has a comment in it.
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+})";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("6: SHADER missing END command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ShaderExtraParameter) {
+ std::string in = R"(#!amber
+SHADER geometry shader_name GLSL INVALID
+# Shader has a comment in it.
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: extra parameters after SHADER command", r.Error());
+}
+
+using AmberScriptParserShaderTypeTest = testing::TestWithParam<ShaderTypeData>;
+TEST_P(AmberScriptParserShaderTypeTest, ShaderFormats) {
+ auto test_data = GetParam();
+
+ std::string shader_result = R"(
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+)";
+
+ std::string in = "SHADER " + std::string(test_data.name) + R"( my_shader GLSL
+)" + shader_result +
+ "END";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ const auto& shaders = script->GetShaders();
+ ASSERT_EQ(1U, shaders.size());
+
+ const auto* shader = shaders[0].get();
+ EXPECT_EQ("my_shader", shader->GetName());
+ EXPECT_EQ(test_data.type, shader->GetType());
+ EXPECT_EQ(ShaderFormat::kGlsl, shader->GetFormat());
+ EXPECT_EQ(shader_result, shader->GetData());
+}
+INSTANTIATE_TEST_CASE_P(
+ AmberScriptParserTestsShaderType,
+ AmberScriptParserShaderTypeTest,
+ testing::Values(ShaderTypeData{"vertex", ShaderType::kVertex},
+ ShaderTypeData{"fragment", ShaderType::kFragment},
+ ShaderTypeData{"geometry", ShaderType::kGeometry},
+ ShaderTypeData{"tessellation_evaluation",
+ ShaderType::kTessellationEvaluation},
+ ShaderTypeData{"tessellation_control",
+ ShaderType::kTessellationControl},
+ ShaderTypeData{"compute", ShaderType::kCompute}), );
+
+using AmberScriptParserShaderFormatTest =
+ testing::TestWithParam<ShaderFormatData>;
+TEST_P(AmberScriptParserShaderFormatTest, ShaderFormats) {
+ auto test_data = GetParam();
+
+ std::string shader_result = R"(void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+)";
+
+ std::string in = "SHADER vertex my_shader " + std::string(test_data.name) +
+ "\n" + shader_result + "END";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ const auto& shaders = script->GetShaders();
+ ASSERT_EQ(1U, shaders.size());
+
+ const auto* shader = shaders[0].get();
+ EXPECT_EQ("my_shader", shader->GetName());
+ EXPECT_EQ(ShaderType::kVertex, shader->GetType());
+ EXPECT_EQ(test_data.format, shader->GetFormat());
+ EXPECT_EQ(shader_result, shader->GetData());
+}
+INSTANTIATE_TEST_CASE_P(
+ AmberScriptParserTestsShaderFormat,
+ AmberScriptParserShaderFormatTest,
+ testing::Values(ShaderFormatData{"GLSL", ShaderFormat::kGlsl},
+ ShaderFormatData{"SPIRV-ASM", ShaderFormat::kSpirvAsm},
+ ShaderFormatData{"SPIRV-HEX", ShaderFormat::kSpirvHex}), );
+
+TEST_F(AmberScriptParserTest, DuplicateShaderName) {
+ std::string in = R"(
+SHADER vertex my_shader GLSL
+# shader
+END
+SHADER fragment my_shader GLSL
+# another shader
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("7: duplicate shader name provided", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, Pipeline) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ EXPECT_EQ(2U, script->GetShaders().size());
+
+ const auto& pipelines = script->GetPipelines();
+ ASSERT_EQ(1U, pipelines.size());
+
+ const auto* pipeline = pipelines[0].get();
+ EXPECT_EQ("my_pipeline", pipeline->GetName());
+ EXPECT_EQ(PipelineType::kGraphics, pipeline->GetType());
+
+ const auto& shaders = pipeline->GetShaders();
+ ASSERT_EQ(2U, shaders.size());
+
+ ASSERT_TRUE(shaders[0].GetShader() != nullptr);
+ EXPECT_EQ("my_shader", shaders[0].GetShader()->GetName());
+ EXPECT_EQ(ShaderType::kVertex, shaders[0].GetShader()->GetType());
+ EXPECT_EQ(static_cast<uint32_t>(0),
+ shaders[0].GetShaderOptimizations().size());
+
+ ASSERT_TRUE(shaders[1].GetShader() != nullptr);
+ EXPECT_EQ("my_fragment", shaders[1].GetShader()->GetName());
+ EXPECT_EQ(ShaderType::kFragment, shaders[1].GetShader()->GetType());
+ EXPECT_EQ(static_cast<uint32_t>(0),
+ shaders[1].GetShaderOptimizations().size());
+}
+
+TEST_F(AmberScriptParserTest, PipelineMissingEnd) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("5: PIPELINE missing END command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineWithExtraParams) {
+ std::string in = R"(
+PIPELINE graphics my_pipeline INVALID
+ ATTACH my_shader
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: extra parameters after PIPELINE command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineInvalidType) {
+ std::string in = "PIPELINE my_name\nEND";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: unknown pipeline type: my_name", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineMissingName) {
+ std::string in = "PIPELINE compute\nEND";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: invalid token when looking for pipeline name", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineWithInvalidTokenType) {
+ std::string in = "PIPELINE 123 my_pipeline\nEND";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: invalid token when looking for pipeline type", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineWithInvalidTokenName) {
+ std::string in = "PIPELINE compute 123\nEND";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: invalid token when looking for pipeline name", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineEmpty) {
+ std::string in = "PIPELINE compute my_pipeline\nEND";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: compute pipeline requires a compute shader", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineWithUnknownShader) {
+ std::string in = R"(
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: unknown shader in ATTACH command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineWithUnknownCommand) {
+ std::string in = R"(
+PIPELINE compute my_pipeline
+ SHADER vertex my_shader PASSTHROUGH
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: unknown token in pipeline block: SHADER", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, DuplicatePipelineName) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# Fragment shader
+END
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+END
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("14: duplicate pipeline name provided", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, DuplicateShadersInAPipeline) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_shader
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("5: can not add duplicate shader to pipeline", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, AttachInvalidToken) {
+ std::string in = R"(PIPELINE graphics my_pipeline
+ ATTACH 1234
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("2: invalid token in ATTACH command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, AttachExtraParameter) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH my_shader INVALID
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: extra parameters after ATTACH command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, AttachMissingValue) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("5: invalid token in ATTACH command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, ComputeShaderInGraphicsPipeline) {
+ std::string in = R"(SHADER compute my_shader GLSL
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+END
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("8: can not add a compute shader to a graphics pipeline",
+ r.Error());
+}
+
+using AmberScriptParserPipelineAttachTest =
+ testing::TestWithParam<ShaderTypeData>;
+TEST_P(AmberScriptParserPipelineAttachTest, GraphicsShaderInComputePipeline) {
+ auto test_data = GetParam();
+
+ std::string in = "SHADER " + std::string(test_data.name) + R"( my_shader GLSL
+void main() {
+ gl_FragColor = vec3(2, 3, 4);
+}
+END
+
+PIPELINE compute my_pipeline
+ ATTACH my_shader
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("8: only compute shaders allowed in a compute pipeline", r.Error());
+}
+INSTANTIATE_TEST_CASE_P(
+ AmberScriptParserPipelineAttachTests,
+ AmberScriptParserPipelineAttachTest,
+ testing::Values(ShaderTypeData{"vertex", ShaderType::kVertex},
+ ShaderTypeData{"fragment", ShaderType::kFragment},
+ ShaderTypeData{"geometry", ShaderType::kGeometry},
+ ShaderTypeData{"tessellation_evaluation",
+ ShaderType::kTessellationEvaluation},
+ ShaderTypeData{"tessellation_control",
+ ShaderType::kTessellationControl}), );
+
+TEST_F(AmberScriptParserTest, PipelineEntryPoint) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ ATTACH my_fragment
+
+ ENTRY_POINT my_shader green
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ const auto& pipelines = script->GetPipelines();
+ ASSERT_EQ(1U, pipelines.size());
+
+ const auto* pipeline = pipelines[0].get();
+ const auto& shaders = pipeline->GetShaders();
+ ASSERT_EQ(2U, shaders.size());
+
+ ASSERT_TRUE(shaders[0].GetShader() != nullptr);
+ EXPECT_EQ(ShaderType::kVertex, shaders[0].GetShader()->GetType());
+ EXPECT_EQ("green", shaders[0].GetEntryPoint());
+
+ ASSERT_TRUE(shaders[1].GetShader() != nullptr);
+ EXPECT_EQ(ShaderType::kFragment, shaders[1].GetShader()->GetType());
+ EXPECT_EQ("main", shaders[1].GetEntryPoint());
+}
+
+TEST_F(AmberScriptParserTest, PipelineEntryPointWithInvalidValue) {
+ std::string in = R"(
+SHADER compute my_compute GLSL
+# Compute Shader
+END
+PIPELINE compute my_pipeline
+ ATTACH my_compute
+ ENTRY_POINT my_compute 1234
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("7: invalid value in ENTRY_POINT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineEntryPointMissingValue) {
+ std::string in = R"(
+SHADER compute my_compute GLSL
+# Compute Shader
+END
+PIPELINE compute my_pipeline
+ ATTACH my_compute
+ ENTRY_POINT
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("8: missing shader name in ENTRY_POINT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineEntryPointMissingEntryPointName) {
+ std::string in = R"(
+SHADER compute my_compute GLSL
+# Compute Shader
+END
+PIPELINE compute my_pipeline
+ ATTACH my_compute
+ ENTRY_POINT my_compute
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("8: invalid value in ENTRY_POINT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineEntryPointExtraParameter) {
+ std::string in = R"(
+SHADER compute my_compute GLSL
+# Compute Shader
+END
+PIPELINE compute my_pipeline
+ ATTACH my_compute
+ ENTRY_POINT my_compute green INVALID
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("7: extra parameters after ENTRY_POINT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineMultipleEntryPointsForOneShader) {
+ std::string in = R"(
+SHADER compute my_compute GLSL
+# Compute Shader
+END
+PIPELINE compute my_pipeline
+ ATTACH my_compute
+ ENTRY_POINT my_compute green
+ ENTRY_POINT my_compute red
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("8: multiple entry points given for the same shader", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineEntryPointForInvalidShader) {
+ std::string in = R"(
+SHADER compute my_compute GLSL
+# Compute Shader
+END
+PIPELINE compute my_pipeline
+ ATTACH my_compute
+ ENTRY_POINT INVALID green
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("7: unknown shader in ENTRY_POINT command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptimization) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+SHADER fragment my_fragment GLSL
+# GLSL Shader
+END
+SHADER geometry my_geom GLSL
+# Geom shader
+END
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ SHADER_OPTIMIZATION my_shader
+ opt1
+ opt_second
+ END
+
+ ATTACH my_fragment
+ SHADER_OPTIMIZATION my_fragment
+ another_optimization
+ third
+ END
+
+ ATTACH my_geom
+ SHADER_OPTIMIZATION my_geom
+ END
+END
+)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToAmberScript(parser.GetScript());
+ const auto& pipelines = script->GetPipelines();
+ ASSERT_EQ(1U, pipelines.size());
+
+ const auto* pipeline = pipelines[0].get();
+ const auto& shaders = pipeline->GetShaders();
+ ASSERT_EQ(3U, shaders.size());
+
+ ASSERT_TRUE(shaders[0].GetShader() != nullptr);
+ EXPECT_EQ(ShaderType::kVertex, shaders[0].GetShader()->GetType());
+ std::vector<std::string> my_shader_opts = {"opt1", "opt_second"};
+ EXPECT_EQ(my_shader_opts, shaders[0].GetShaderOptimizations());
+
+ ASSERT_TRUE(shaders[1].GetShader() != nullptr);
+ EXPECT_EQ(ShaderType::kFragment, shaders[1].GetShader()->GetType());
+ std::vector<std::string> my_fragment_opts = {"another_optimization", "third"};
+ EXPECT_EQ(my_fragment_opts, shaders[1].GetShaderOptimizations());
+
+ ASSERT_TRUE(shaders[2].GetShader() != nullptr);
+ EXPECT_EQ(ShaderType::kGeometry, shaders[2].GetShader()->GetType());
+ std::vector<std::string> my_geom_opts = {};
+ EXPECT_EQ(my_geom_opts, shaders[2].GetShaderOptimizations());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptmizationInvalidShader) {
+ std::string in = R"(
+PIPELINE graphics my_pipeline
+SHADER_OPTIMIZATION invalid_shader
+ opt1
+ opt_second
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("3: unknown shader in SHADER_OPTIMIZATION command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptmizationMissingShader) {
+ std::string in = R"(
+PIPELINE graphics my_pipeline
+SHADER_OPTIMIZATION
+ opt1
+ opt_second
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("4: missing shader name in SHADER_OPTIMIZATION command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptmizationnUnAttachedShader) {
+ std::string in = R"(
+SHADER vertex my_vertex PASSTHROUGH
+PIPELINE graphics my_pipeline
+ SHADER_OPTIMIZATION my_vertex
+ opt1
+ opt_second
+ END
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("7: unknown shader specified for optimizations: my_vertex",
+ r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptimizationMissingEnd) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ SHADER_OPTIMIZATION my_shader
+ opt1
+ opt_second)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("7: SHADER_OPTIMIZATION missing END command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptimizationExtraParams) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ SHADER_OPTIMIZATION my_shader EXTRA
+ opt1
+ opt_second
+ END
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("5: extra parameters after SHADER_OPTIMIZATION command", r.Error());
+}
+
+TEST_F(AmberScriptParserTest, PipelineShaderOptimizationNonStringParam) {
+ std::string in = R"(
+SHADER vertex my_shader PASSTHROUGH
+PIPELINE graphics my_pipeline
+ ATTACH my_shader
+ SHADER_OPTIMIZATION my_shader
+ 123
+ opt
+ END
+END)";
+
+ Parser parser;
+ Result r = parser.Parse(in);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("6: SHADER_OPTIMIZATION options must be strings", r.Error());
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/amberscript/pipeline.cc b/src/amberscript/pipeline.cc
new file mode 100644
index 0000000..94f498b
--- /dev/null
+++ b/src/amberscript/pipeline.cc
@@ -0,0 +1,144 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amberscript/pipeline.h"
+
+#include <algorithm>
+#include <set>
+
+namespace amber {
+namespace amberscript {
+
+Pipeline::ShaderInfo::ShaderInfo(const Shader* shader)
+ : shader_(shader), entry_point_("main") {}
+
+Pipeline::ShaderInfo::ShaderInfo(const ShaderInfo&) = default;
+
+Pipeline::ShaderInfo::~ShaderInfo() = default;
+
+Pipeline::Pipeline(PipelineType type) : pipeline_type_(type) {}
+
+Pipeline::~Pipeline() = default;
+
+Result Pipeline::AddShader(const Shader* shader) {
+ if (!shader)
+ return Result("shader can not be null when attached to pipeline");
+
+ if (pipeline_type_ == PipelineType::kCompute &&
+ shader->GetType() != ShaderType::kCompute) {
+ return Result("only compute shaders allowed in a compute pipeline");
+ }
+ if (pipeline_type_ == PipelineType::kGraphics &&
+ shader->GetType() == ShaderType::kCompute) {
+ return Result("can not add a compute shader to a graphics pipeline");
+ }
+
+ for (const auto& info : shaders_) {
+ const auto* is = info.GetShader();
+ if (is == shader)
+ return Result("can not add duplicate shader to pipeline");
+ if (is->GetType() == shader->GetType())
+ return Result("can not add duplicate shader type to pipeline");
+ }
+
+ shaders_.emplace_back(shader);
+ return {};
+}
+
+Result Pipeline::SetShaderOptimizations(const Shader* shader,
+ const std::vector<std::string>& opts) {
+ if (!shader)
+ return Result("invalid shader specified for optimizations");
+
+ std::set<std::string> seen;
+ for (const auto& opt : opts) {
+ if (seen.count(opt) != 0)
+ return Result("duplicate optimization flag (" + opt + ") set on shader");
+
+ seen.insert(opt);
+ }
+
+ for (auto& info : shaders_) {
+ const auto* is = info.GetShader();
+ if (is == shader) {
+ info.SetShaderOptimizations(opts);
+ return {};
+ }
+ }
+
+ return Result("unknown shader specified for optimizations: " +
+ shader->GetName());
+}
+
+Result Pipeline::SetShaderEntryPoint(const Shader* shader,
+ const std::string& name) {
+ if (!shader)
+ return Result("invalid shader specified for entry point");
+ if (name.empty())
+ return Result("entry point should not be blank");
+
+ for (auto& info : shaders_) {
+ if (info.GetShader() == shader) {
+ if (info.GetEntryPoint() != "main")
+ return Result("multiple entry points given for the same shader");
+
+ info.SetEntryPoint(name);
+ return {};
+ }
+ }
+
+ return Result("unknown shader specified for entry point: " +
+ shader->GetName());
+}
+
+Result Pipeline::Validate() const {
+ if (pipeline_type_ == PipelineType::kGraphics)
+ return ValidateGraphics();
+ return ValidateCompute();
+}
+
+Result Pipeline::ValidateGraphics() const {
+ if (shaders_.empty())
+ return Result("graphics pipeline requires vertex and fragment shaders");
+
+ bool found_vertex = false;
+ bool found_fragment = false;
+ for (const auto& info : shaders_) {
+ const auto* is = info.GetShader();
+ if (is->GetType() == ShaderType::kVertex)
+ found_vertex = true;
+ if (is->GetType() == ShaderType::kFragment)
+ found_fragment = true;
+ if (found_vertex && found_fragment)
+ break;
+ }
+
+ if (!found_vertex && !found_fragment)
+ return Result("graphics pipeline requires vertex and fragment shaders");
+ if (!found_vertex)
+ return Result("graphics pipeline requires a vertex shader");
+ if (!found_fragment)
+ return Result("graphics pipeline requires a fragment shader");
+ return {};
+}
+
+Result Pipeline::ValidateCompute() const {
+ if (shaders_.empty())
+ return Result("compute pipeline requires a compute shader");
+
+ return {};
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/amberscript/pipeline.h b/src/amberscript/pipeline.h
new file mode 100644
index 0000000..3bf5d68
--- /dev/null
+++ b/src/amberscript/pipeline.h
@@ -0,0 +1,85 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_AMBERSCRIPT_PIPELINE_H_
+#define SRC_AMBERSCRIPT_PIPELINE_H_
+
+#include <string>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/amberscript/shader.h"
+
+namespace amber {
+namespace amberscript {
+
+enum class PipelineType { kCompute = 0, kGraphics };
+
+class Pipeline {
+ public:
+ class ShaderInfo {
+ public:
+ ShaderInfo(const Shader*);
+ ShaderInfo(const ShaderInfo&);
+ ~ShaderInfo();
+
+ void SetShaderOptimizations(const std::vector<std::string>& opts) {
+ shader_optimizations_ = opts;
+ }
+ const std::vector<std::string>& GetShaderOptimizations() const {
+ return shader_optimizations_;
+ }
+
+ const Shader* GetShader() const { return shader_; }
+
+ void SetEntryPoint(const std::string& ep) { entry_point_ = ep; }
+ std::string GetEntryPoint() const { return entry_point_; }
+
+ private:
+ const Shader* shader_ = nullptr;
+ std::vector<std::string> shader_optimizations_;
+ std::string entry_point_;
+ };
+
+ Pipeline(PipelineType type);
+ ~Pipeline();
+
+ PipelineType GetType() const { return pipeline_type_; }
+
+ void SetName(const std::string& name) { name_ = name; }
+ const std::string& GetName() const { return name_; }
+
+ Result AddShader(const Shader*);
+ const std::vector<ShaderInfo>& GetShaders() const { return shaders_; }
+
+ Result SetShaderEntryPoint(const Shader* shader, const std::string& name);
+ Result SetShaderOptimizations(const Shader* shader,
+ const std::vector<std::string>& opts);
+
+ // Validates that the pipeline has been created correctly.
+ Result Validate() const;
+
+ private:
+ Result ValidateGraphics() const;
+ Result ValidateCompute() const;
+
+ PipelineType pipeline_type_ = PipelineType::kCompute;
+ std::string name_;
+ std::vector<ShaderInfo> shaders_;
+};
+
+} // namespace amberscript
+} // namespace amber
+
+#endif // SRC_AMBERSCRIPT_PIPELINE_H_
diff --git a/src/amberscript/pipeline_test.cc b/src/amberscript/pipeline_test.cc
new file mode 100644
index 0000000..4d6f366
--- /dev/null
+++ b/src/amberscript/pipeline_test.cc
@@ -0,0 +1,331 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amberscript/pipeline.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+namespace amberscript {
+namespace {
+
+struct ShaderTypeData {
+ ShaderType type;
+};
+
+} // namespace
+
+using AmberScriptPipelineTest = testing::Test;
+
+TEST_F(AmberScriptPipelineTest, AddShader) {
+ Shader v(ShaderType::kVertex);
+ Shader f(ShaderType::kFragment);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&f);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ const auto& shaders = p.GetShaders();
+ EXPECT_EQ(2U, shaders.size());
+
+ EXPECT_EQ(&v, shaders[0].GetShader());
+ EXPECT_EQ(&f, shaders[1].GetShader());
+}
+
+TEST_F(AmberScriptPipelineTest, MissingShader) {
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(nullptr);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("shader can not be null when attached to pipeline", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, DuplicateShaders) {
+ Shader v(ShaderType::kVertex);
+ Shader f(ShaderType::kFragment);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&f);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&v);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("can not add duplicate shader to pipeline", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, DuplicateShaderType) {
+ Shader v(ShaderType::kVertex);
+ Shader f(ShaderType::kVertex);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&f);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("can not add duplicate shader type to pipeline", r.Error());
+}
+
+using AmberScriptPipelineComputePipelineTest =
+ testing::TestWithParam<ShaderTypeData>;
+TEST_P(AmberScriptPipelineComputePipelineTest,
+ SettingGraphicsShaderToComputePipeline) {
+ const auto test_data = GetParam();
+
+ Shader s(test_data.type);
+
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.AddShader(&s);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("only compute shaders allowed in a compute pipeline", r.Error());
+}
+INSTANTIATE_TEST_CASE_P(
+ AmberScriptPipelineComputePipelineTests,
+ AmberScriptPipelineComputePipelineTest,
+ testing::Values(ShaderTypeData{ShaderType::kVertex},
+ ShaderTypeData{ShaderType::kFragment},
+ ShaderTypeData{ShaderType::kGeometry},
+ ShaderTypeData{ShaderType::kTessellationEvaluation},
+ ShaderTypeData{ShaderType::kTessellationControl}), );
+
+TEST_F(AmberScriptPipelineTest, SettingComputeShaderToGraphicsPipeline) {
+ Shader c(ShaderType::kCompute);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&c);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("can not add a compute shader to a graphics pipeline", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, SetShaderOptimizations) {
+ Shader v(ShaderType::kVertex);
+ Shader f(ShaderType::kFragment);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&f);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ std::vector<std::string> first = {"First", "Second"};
+ std::vector<std::string> second = {"Third", "Forth"};
+
+ r = p.SetShaderOptimizations(&f, first);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.SetShaderOptimizations(&v, second);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ const auto& shaders = p.GetShaders();
+ EXPECT_EQ(2U, shaders.size());
+ EXPECT_EQ(second, shaders[0].GetShaderOptimizations());
+ EXPECT_EQ(first, shaders[1].GetShaderOptimizations());
+}
+
+TEST_F(AmberScriptPipelineTest, DuplicateShaderOptimizations) {
+ Shader v(ShaderType::kVertex);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ std::vector<std::string> data = {"One", "One"};
+ r = p.SetShaderOptimizations(&v, data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("duplicate optimization flag (One) set on shader", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, SetOptimizationForMissingShader) {
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.SetShaderOptimizations(nullptr, {"One", "Two"});
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("invalid shader specified for optimizations", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, SetOptimizationForInvalidShader) {
+ Shader v(ShaderType::kVertex);
+ v.SetName("my_shader");
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.SetShaderOptimizations(&v, {"One", "Two"});
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("unknown shader specified for optimizations: my_shader", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest,
+ GraphicsPipelineRequiresVertexAndFragmentShader) {
+ Shader v(ShaderType::kVertex);
+ Shader f(ShaderType::kFragment);
+ Shader g(ShaderType::kGeometry);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&g);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&f);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.Validate();
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+}
+
+TEST_F(AmberScriptPipelineTest, GraphicsPipelineMissingFragmentShader) {
+ Shader v(ShaderType::kVertex);
+ Shader g(ShaderType::kGeometry);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&v);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&g);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.Validate();
+ EXPECT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("graphics pipeline requires a fragment shader", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, GraphicsPipelineMissingVertexShader) {
+ Shader f(ShaderType::kFragment);
+ Shader g(ShaderType::kGeometry);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&g);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.AddShader(&f);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.Validate();
+ EXPECT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("graphics pipeline requires a vertex shader", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest,
+ GraphicsPipelineMissingVertexAndFragmentShader) {
+ Shader g(ShaderType::kGeometry);
+
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.AddShader(&g);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.Validate();
+ EXPECT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("graphics pipeline requires vertex and fragment shaders",
+ r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, GraphicsPipelineWihoutShaders) {
+ Pipeline p(PipelineType::kGraphics);
+ Result r = p.Validate();
+ EXPECT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("graphics pipeline requires vertex and fragment shaders",
+ r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, ComputePipelineRequiresComputeShader) {
+ Shader c(ShaderType::kCompute);
+
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.AddShader(&c);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.Validate();
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+}
+
+TEST_F(AmberScriptPipelineTest, ComputePipelineWithoutShader) {
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.Validate();
+ EXPECT_FALSE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ("compute pipeline requires a compute shader", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, SetEntryPointForMissingShader) {
+ Shader c(ShaderType::kCompute);
+ c.SetName("my_shader");
+
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.SetShaderEntryPoint(&c, "test");
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("unknown shader specified for entry point: my_shader", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, SetEntryPointForNullShader) {
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.SetShaderEntryPoint(nullptr, "test");
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("invalid shader specified for entry point", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, SetBlankEntryPoint) {
+ Shader c(ShaderType::kCompute);
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.AddShader(&c);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.SetShaderEntryPoint(&c, "");
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("entry point should not be blank", r.Error());
+}
+
+TEST_F(AmberScriptPipelineTest, ShaderDefaultEntryPoint) {
+ Shader c(ShaderType::kCompute);
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.AddShader(&c);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ const auto& shaders = p.GetShaders();
+ ASSERT_EQ(1U, shaders.size());
+ EXPECT_EQ("main", shaders[0].GetEntryPoint());
+}
+
+TEST_F(AmberScriptPipelineTest, SetShaderEntryPoint) {
+ Shader c(ShaderType::kCompute);
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.AddShader(&c);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.SetShaderEntryPoint(&c, "my_main");
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ const auto& shaders = p.GetShaders();
+ ASSERT_EQ(1U, shaders.size());
+ EXPECT_EQ("my_main", shaders[0].GetEntryPoint());
+}
+
+TEST_F(AmberScriptPipelineTest, SetEntryPointMulitpleTimes) {
+ Shader c(ShaderType::kCompute);
+ Pipeline p(PipelineType::kCompute);
+ Result r = p.AddShader(&c);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.SetShaderEntryPoint(&c, "my_main");
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ r = p.SetShaderEntryPoint(&c, "another_main");
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("multiple entry points given for the same shader", r.Error());
+}
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/amberscript/script.cc b/src/amberscript/script.cc
new file mode 100644
index 0000000..17bfedc
--- /dev/null
+++ b/src/amberscript/script.cc
@@ -0,0 +1,30 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amberscript/script.h"
+
+namespace amber {
+namespace amberscript {
+
+Script::Script() : amber::Script(ScriptType::kAmberScript) {}
+
+Script::~Script() = default;
+
+} // namespace amberscript
+
+const amberscript::Script* ToAmberScript(const amber::Script* s) {
+ return static_cast<const amberscript::Script*>(s);
+}
+
+} // namespace amber
diff --git a/src/amberscript/script.h b/src/amberscript/script.h
new file mode 100644
index 0000000..79eac96
--- /dev/null
+++ b/src/amberscript/script.h
@@ -0,0 +1,81 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_AMBERSCRIPT_SCRIPT_H_
+#define SRC_AMBERSCRIPT_SCRIPT_H_
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "src/amberscript/pipeline.h"
+#include "src/amberscript/shader.h"
+#include "src/script.h"
+
+namespace amber {
+namespace amberscript {
+
+class Script : public amber::Script {
+ public:
+ Script();
+ ~Script() override;
+
+ Result AddShader(std::unique_ptr<Shader> shader) {
+ if (name_to_shader_.count(shader->GetName()) > 0)
+ return Result("duplicate shader name provided");
+
+ shaders_.push_back(std::move(shader));
+ name_to_shader_[shaders_.back()->GetName()] = shaders_.back().get();
+ return {};
+ }
+ const std::vector<std::unique_ptr<Shader>>& GetShaders() const {
+ return shaders_;
+ }
+
+ Result AddPipeline(std::unique_ptr<Pipeline> pipeline) {
+ if (name_to_pipeline_.count(pipeline->GetName()) > 0)
+ return Result("duplicate pipeline name provided");
+
+ pipelines_.push_back(std::move(pipeline));
+ name_to_pipeline_[pipelines_.back()->GetName()] = pipelines_.back().get();
+ return {};
+ }
+ const std::vector<std::unique_ptr<Pipeline>>& GetPipelines() const {
+ return pipelines_;
+ }
+
+ Shader* GetShader(const std::string& name) const {
+ auto it = name_to_shader_.find(name);
+ return it == name_to_shader_.end() ? nullptr : it->second;
+ }
+
+ Pipeline* GetPipeline(const std::string& name) const {
+ auto it = name_to_pipeline_.find(name);
+ return it == name_to_pipeline_.end() ? nullptr : it->second;
+ }
+
+ private:
+ std::map<std::string, Shader*> name_to_shader_;
+ std::map<std::string, Pipeline*> name_to_pipeline_;
+ std::vector<std::unique_ptr<Shader>> shaders_;
+ std::vector<std::unique_ptr<Pipeline>> pipelines_;
+};
+
+} // namespace amberscript
+
+const amberscript::Script* ToAmberScript(const amber::Script* s);
+
+} // namespace amber
+
+#endif // SRC_AMBERSCRIPT_SCRIPT_H_
diff --git a/src/amberscript/shader.cc b/src/amberscript/shader.cc
new file mode 100644
index 0000000..7d2cf5f
--- /dev/null
+++ b/src/amberscript/shader.cc
@@ -0,0 +1,25 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/amberscript/shader.h"
+
+namespace amber {
+namespace amberscript {
+
+Shader::Shader(ShaderType type) : shader_type_(type) {}
+
+Shader::~Shader() = default;
+
+} // namespace amberscript
+} // namespace amber
diff --git a/src/amberscript/shader.h b/src/amberscript/shader.h
new file mode 100644
index 0000000..eb6e1a2
--- /dev/null
+++ b/src/amberscript/shader.h
@@ -0,0 +1,51 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_AMBERSCRIPT_SHADER_H_
+#define SRC_AMBERSCRIPT_SHADER_H_
+
+#include <string>
+
+#include "src/shader_data.h"
+
+namespace amber {
+namespace amberscript {
+
+class Shader {
+ public:
+ Shader(ShaderType type);
+ ~Shader();
+
+ ShaderType GetType() const { return shader_type_; }
+
+ void SetName(const std::string& name) { name_ = name; }
+ const std::string& GetName() const { return name_; }
+
+ void SetFormat(ShaderFormat fmt) { shader_format_ = fmt; }
+ ShaderFormat GetFormat() const { return shader_format_; }
+
+ void SetData(const std::string& data) { data_ = data; }
+ const std::string& GetData() const { return data_; }
+
+ private:
+ ShaderType shader_type_;
+ ShaderFormat shader_format_;
+ std::string data_;
+ std::string name_;
+};
+
+} // namespace amberscript
+} // namespace amber
+
+#endif // SRC_AMBERSCRIPT_SHADER_H_
diff --git a/src/buffer_data.h b/src/buffer_data.h
new file mode 100644
index 0000000..6b6ada6
--- /dev/null
+++ b/src/buffer_data.h
@@ -0,0 +1,24 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_BUFFER_DATA_H_
+#define SRC_BUFFER_DATA_H_
+
+namespace amber {
+
+enum class BufferType { kFramebuffer = 0, kVertexData, kIndices };
+
+} // namespace amber
+
+#endif // SRC_BUFFER_DATA_H_
diff --git a/src/cast_hash.h b/src/cast_hash.h
new file mode 100644
index 0000000..c7afbf0
--- /dev/null
+++ b/src/cast_hash.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_CAST_HASH_H_
+#define SRC_CAST_HASH_H_
+
+namespace amber {
+
+// A hash implementation for types that can trivially be up-cast to a size_t.
+// For example, use this as a hasher for an enum whose underlying type is
+// an integer no wider than size_t.
+//
+// The need for this was a defect in the C++11 library, and has been fixed
+// in C++14. http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2148
+template <typename T>
+struct CastHash {
+ size_t operator()(const T& value) const { return static_cast<size_t>(value); }
+};
+
+} // namespace amber
+
+#endif // SRC_CAST_HASH_H_
diff --git a/src/command.cc b/src/command.cc
new file mode 100644
index 0000000..a89a75d
--- /dev/null
+++ b/src/command.cc
@@ -0,0 +1,132 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/command.h"
+
+namespace amber {
+
+Command::Command(Type type) : command_type_(type) {}
+
+Command::~Command() = default;
+
+ClearCommand* Command::AsClear() {
+ return static_cast<ClearCommand*>(this);
+}
+
+ClearColorCommand* Command::AsClearColor() {
+ return static_cast<ClearColorCommand*>(this);
+}
+
+ClearDepthCommand* Command::AsClearDepth() {
+ return static_cast<ClearDepthCommand*>(this);
+}
+
+ClearStencilCommand* Command::AsClearStencil() {
+ return static_cast<ClearStencilCommand*>(this);
+}
+
+ComputeCommand* Command::AsCompute() {
+ return static_cast<ComputeCommand*>(this);
+}
+
+DrawArraysCommand* Command::AsDrawArrays() {
+ return static_cast<DrawArraysCommand*>(this);
+}
+
+DrawRectCommand* Command::AsDrawRect() {
+ return static_cast<DrawRectCommand*>(this);
+}
+
+EntryPointCommand* Command::AsEntryPoint() {
+ return static_cast<EntryPointCommand*>(this);
+}
+
+PatchParameterVerticesCommand* Command::AsPatchParameterVertices() {
+ return static_cast<PatchParameterVerticesCommand*>(this);
+}
+
+ProbeCommand* Command::AsProbe() {
+ return static_cast<ProbeCommand*>(this);
+}
+
+BufferCommand* Command::AsBuffer() {
+ return static_cast<BufferCommand*>(this);
+}
+
+ProbeSSBOCommand* Command::AsProbeSSBO() {
+ return static_cast<ProbeSSBOCommand*>(this);
+}
+
+ToleranceCommand* Command::AsTolerance() {
+ return static_cast<ToleranceCommand*>(this);
+}
+
+DrawRectCommand::DrawRectCommand(PipelineData data)
+ : Command(Type::kDrawRect), data_(data) {}
+
+DrawRectCommand::~DrawRectCommand() = default;
+
+DrawArraysCommand::DrawArraysCommand(PipelineData data)
+ : Command(Type::kDrawArrays), data_(data) {}
+
+DrawArraysCommand::~DrawArraysCommand() = default;
+
+ComputeCommand::ComputeCommand(PipelineData data)
+ : Command(Type::kCompute), data_(data) {}
+
+ComputeCommand::~ComputeCommand() = default;
+
+ProbeCommand::ProbeCommand() : Command(Type::kProbe) {}
+
+ProbeCommand::~ProbeCommand() = default;
+
+BufferCommand::BufferCommand(BufferType type)
+ : Command(Type::kBuffer), buffer_type_(type) {}
+
+BufferCommand::~BufferCommand() = default;
+
+ProbeSSBOCommand::ProbeSSBOCommand() : Command(Type::kProbeSSBO) {}
+
+ProbeSSBOCommand::~ProbeSSBOCommand() = default;
+
+ToleranceCommand::ToleranceCommand() : Command(Type::kTolerance) {}
+
+ToleranceCommand::~ToleranceCommand() = default;
+
+ClearCommand::ClearCommand() : Command(Type::kClear) {}
+
+ClearCommand::~ClearCommand() = default;
+
+ClearColorCommand::ClearColorCommand() : Command(Type::kClearColor) {}
+
+ClearColorCommand::~ClearColorCommand() = default;
+
+ClearDepthCommand::ClearDepthCommand() : Command(Type::kClearDepth) {}
+
+ClearDepthCommand::~ClearDepthCommand() = default;
+
+ClearStencilCommand::ClearStencilCommand() : Command(Type::kClearStencil) {}
+
+ClearStencilCommand::~ClearStencilCommand() = default;
+
+PatchParameterVerticesCommand::PatchParameterVerticesCommand()
+ : Command(Type::kPatchParameterVertices) {}
+
+PatchParameterVerticesCommand::~PatchParameterVerticesCommand() = default;
+
+EntryPointCommand::EntryPointCommand() : Command(Type::kEntryPoint) {}
+
+EntryPointCommand::~EntryPointCommand() = default;
+
+} // namespace amber
diff --git a/src/command.h b/src/command.h
new file mode 100644
index 0000000..7186b62
--- /dev/null
+++ b/src/command.h
@@ -0,0 +1,454 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_COMMAND_H_
+#define SRC_COMMAND_H_
+
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include "src/command_data.h"
+#include "src/datum_type.h"
+#include "src/pipeline_data.h"
+#include "src/shader_data.h"
+#include "src/value.h"
+
+namespace amber {
+
+class ClearCommand;
+class ClearColorCommand;
+class ClearDepthCommand;
+class ClearStencilCommand;
+class ComputeCommand;
+class DrawArraysCommand;
+class DrawRectCommand;
+class EntryPointCommand;
+class PatchParameterVerticesCommand;
+class ProbeCommand;
+class ProbeSSBOCommand;
+class BufferCommand;
+class ToleranceCommand;
+
+class Command {
+ public:
+ enum class Type : uint8_t {
+ kClear = 0,
+ kClearColor,
+ kClearDepth,
+ kClearStencil,
+ kCompute,
+ kDrawArrays,
+ kDrawRect,
+ kEntryPoint,
+ kPatchParameterVertices,
+ kPipelineProperties,
+ kProbe,
+ kProbeSSBO,
+ kBuffer,
+ kTolerance,
+ };
+
+ virtual ~Command();
+
+ bool IsDrawRect() const { return command_type_ == Type::kDrawRect; }
+ bool IsDrawArrays() const { return command_type_ == Type::kDrawArrays; }
+ bool IsCompute() const { return command_type_ == Type::kCompute; }
+ bool IsProbe() const { return command_type_ == Type::kProbe; }
+ bool IsProbeSSBO() const { return command_type_ == Type::kProbeSSBO; }
+ bool IsBuffer() const { return command_type_ == Type::kBuffer; }
+ bool IsTolerance() const { return command_type_ == Type::kTolerance; }
+ bool IsClear() const { return command_type_ == Type::kClear; }
+ bool IsClearColor() const { return command_type_ == Type::kClearColor; }
+ bool IsClearDepth() const { return command_type_ == Type::kClearDepth; }
+ bool IsClearStencil() const { return command_type_ == Type::kClearStencil; }
+ bool IsPatchParameterVertices() const {
+ return command_type_ == Type::kPatchParameterVertices;
+ }
+ bool IsEntryPoint() const { return command_type_ == Type::kEntryPoint; }
+
+ ClearCommand* AsClear();
+ ClearColorCommand* AsClearColor();
+ ClearDepthCommand* AsClearDepth();
+ ClearStencilCommand* AsClearStencil();
+ ComputeCommand* AsCompute();
+ DrawArraysCommand* AsDrawArrays();
+ DrawRectCommand* AsDrawRect();
+ EntryPointCommand* AsEntryPoint();
+ PatchParameterVerticesCommand* AsPatchParameterVertices();
+ ProbeCommand* AsProbe();
+ ProbeSSBOCommand* AsProbeSSBO();
+ BufferCommand* AsBuffer();
+ ToleranceCommand* AsTolerance();
+
+ protected:
+ Command(Type type);
+
+ Type command_type_;
+};
+
+class DrawRectCommand : public Command {
+ public:
+ DrawRectCommand(PipelineData data);
+ ~DrawRectCommand() override;
+
+ const PipelineData* GetPipelineData() const { return &data_; }
+
+ void EnableOrtho() { is_ortho_ = true; }
+ bool IsOrtho() const { return is_ortho_; }
+
+ void EnablePatch() { is_patch_ = true; }
+ bool IsPatch() const { return is_patch_; }
+
+ void SetX(float x) { x_ = x; }
+ float GetX() const { return x_; }
+
+ void SetY(float y) { y_ = y; }
+ float GetY() const { return y_; }
+
+ void SetWidth(float w) { width_ = w; }
+ float GetWidth() const { return width_; }
+
+ void SetHeight(float h) { height_ = h; }
+ float GetHeight() const { return height_; }
+
+ private:
+ PipelineData data_;
+ bool is_ortho_ = false;
+ bool is_patch_ = false;
+ float x_ = 0.0;
+ float y_ = 0.0;
+ float width_ = 0.0;
+ float height_ = 0.0;
+};
+
+class DrawArraysCommand : public Command {
+ public:
+ DrawArraysCommand(PipelineData data);
+ ~DrawArraysCommand() override;
+
+ const PipelineData* GetPipelineData() const { return &data_; }
+
+ void EnableIndexed() { is_indexed_ = true; }
+ bool IsIndexed() const { return is_indexed_; }
+
+ void EnableInstanced() { is_instanced_ = true; }
+ bool IsInstanced() const { return is_instanced_; }
+
+ void SetTopology(Topology topo) { topology_ = topo; }
+ Topology GetTopology() const { return topology_; }
+
+ void SetFirstVertexIndex(uint32_t idx) { first_vertex_index_ = idx; }
+ uint32_t GetFirstVertexIndex() const { return first_vertex_index_; }
+
+ void SetVertexCount(uint32_t count) { vertex_count_ = count; }
+ uint32_t GetVertexCount() const { return vertex_count_; }
+
+ void SetInstanceCount(uint32_t count) { instance_count_ = count; }
+ uint32_t GetInstanceCount() const { return instance_count_; }
+
+ private:
+ PipelineData data_;
+ bool is_indexed_ = false;
+ bool is_instanced_ = false;
+ Topology topology_ = Topology::kUnknown;
+ uint32_t first_vertex_index_ = 0;
+ uint32_t vertex_count_ = 0;
+ uint32_t instance_count_ = 0;
+};
+
+class ComputeCommand : public Command {
+ public:
+ ComputeCommand(PipelineData data);
+ ~ComputeCommand() override;
+
+ const PipelineData* GetPipelineData() const { return &data_; }
+
+ void SetX(uint32_t x) { x_ = x; }
+ uint32_t GetX() const { return x_; }
+
+ void SetY(uint32_t y) { y_ = y; }
+ uint32_t GetY() const { return y_; }
+
+ void SetZ(uint32_t z) { z_ = z; }
+ uint32_t GetZ() const { return z_; }
+
+ private:
+ PipelineData data_;
+
+ uint32_t x_ = 0;
+ uint32_t y_ = 0;
+ uint32_t z_ = 0;
+};
+
+class ProbeCommand : public Command {
+ public:
+ ProbeCommand();
+ ~ProbeCommand() override;
+
+ void SetWholeWindow() { is_whole_window_ = true; }
+ bool IsWholeWindow() const { return is_whole_window_; }
+
+ void SetRelative() { is_relative_ = true; }
+ bool IsRelative() const { return is_relative_; }
+
+ void SetIsRGBA() { color_format_ = ColorFormat::kRGBA; }
+ bool IsRGBA() const { return color_format_ == ColorFormat::kRGBA; }
+
+ void SetX(float x) { x_ = x; }
+ float GetX() const { return x_; }
+
+ void SetY(float y) { y_ = y; }
+ float GetY() const { return y_; }
+
+ void SetWidth(float w) { width_ = w; }
+ float GetWidth() const { return width_; }
+
+ void SetHeight(float h) { height_ = h; }
+ float GetHeight() const { return height_; }
+
+ void SetR(float r) { r_ = r; }
+ float GetR() const { return r_; }
+
+ void SetG(float g) { g_ = g; }
+ float GetG() const { return g_; }
+
+ void SetB(float b) { b_ = b; }
+ float GetB() const { return b_; }
+
+ void SetA(float a) { a_ = a; }
+ float GetA() const { return a_; }
+
+ private:
+ enum class ColorFormat {
+ kRGB = 0,
+ kRGBA,
+ };
+
+ bool is_whole_window_ = false;
+ bool is_relative_ = false;
+ ColorFormat color_format_ = ColorFormat::kRGB;
+
+ float x_ = 0.0;
+ float y_ = 0.0;
+ float width_ = 1.0;
+ float height_ = 1.0;
+
+ float r_ = 0.0;
+ float g_ = 0.0;
+ float b_ = 0.0;
+ float a_ = 0.0;
+};
+
+class ProbeSSBOCommand : public Command {
+ public:
+ enum class Comparator {
+ kEqual,
+ kNotEqual,
+ kFuzzyEqual,
+ kLess,
+ kLessOrEqual,
+ kGreater,
+ kGreaterOrEqual
+ };
+
+ ProbeSSBOCommand();
+ ~ProbeSSBOCommand() override;
+
+ void SetComparator(Comparator comp) { comparator_ = comp; }
+ Comparator GetComparator() const { return comparator_; }
+
+ void SetDescriptorSet(uint32_t id) { descriptor_set_id_ = id; }
+ uint32_t GetDescriptorSet() const { return descriptor_set_id_; }
+
+ void SetBinding(uint32_t id) { binding_num_ = id; }
+ uint32_t GetBinding() const { return binding_num_; }
+
+ void SetOffset(uint32_t offset) { offset_ = offset; }
+ uint32_t GetOffset() const { return offset_; }
+
+ void SetDatumType(const DatumType& type) { datum_type_ = type; }
+ const DatumType& GetDatumType() const { return datum_type_; }
+
+ void SetValues(std::vector<Value>&& values) { values_ = std::move(values); }
+ const std::vector<Value>& GetValues() const { return values_; }
+
+ private:
+ Comparator comparator_ = Comparator::kEqual;
+ uint32_t descriptor_set_id_ = 0;
+ uint32_t binding_num_ = 0;
+ uint32_t offset_ = 0;
+ DatumType datum_type_;
+ std::vector<Value> values_;
+};
+
+class BufferCommand : public Command {
+ public:
+ enum class BufferType {
+ kSSBO,
+ kUniform,
+ kPushConstant,
+ };
+
+ BufferCommand(BufferType type);
+ ~BufferCommand() override;
+
+ bool IsSSBO() const { return buffer_type_ == BufferType::kSSBO; }
+ bool IsUniform() const { return buffer_type_ == BufferType::kUniform; }
+ bool IsPushConstant() const {
+ return buffer_type_ == BufferType::kPushConstant;
+ }
+
+ void SetIsSubdata() { is_subdata_ = true; }
+ bool IsSubdata() const { return is_subdata_; }
+
+ void SetDescriptorSet(uint32_t set) { descriptor_set_ = set; }
+ uint32_t GetDescriptorSet() const { return descriptor_set_; }
+
+ void SetBinding(uint32_t num) { binding_num_ = num; }
+ uint32_t GetBinding() const { return binding_num_; }
+
+ void SetOffset(uint32_t offset) { offset_ = offset; }
+ uint32_t GetOffset() const { return offset_; }
+
+ void SetSize(uint32_t size) { size_ = size; }
+ uint32_t GetSize() const { return size_; }
+
+ void SetDatumType(const DatumType& type) { datum_type_ = type; }
+ const DatumType& GetDatumType() const { return datum_type_; }
+
+ void SetValues(std::vector<Value>&& values) { values_ = std::move(values); }
+ const std::vector<Value>& GetValues() const { return values_; }
+
+ private:
+ BufferType buffer_type_;
+ bool is_subdata_ = false;
+ uint32_t descriptor_set_ = 0;
+ uint32_t binding_num_ = 0;
+ uint32_t size_ = 0;
+ uint32_t offset_ = 0;
+ DatumType datum_type_;
+ std::vector<Value> values_;
+};
+
+class ToleranceCommand : public Command {
+ public:
+ struct Tolerance {
+ Tolerance(bool percent, double val) : is_percent(percent), value(val) {}
+
+ bool is_percent = false;
+ double value = 0.0;
+ };
+
+ ToleranceCommand();
+ ~ToleranceCommand() override;
+
+ void AddPercentTolerance(double value) {
+ tolerances_.emplace_back(Tolerance{true, value});
+ }
+ void AddValueTolerance(double value) {
+ tolerances_.emplace_back(Tolerance{false, value});
+ }
+
+ const std::vector<Tolerance>& GetTolerances() const { return tolerances_; }
+
+ private:
+ std::vector<Tolerance> tolerances_;
+};
+
+class ClearCommand : public Command {
+ public:
+ ClearCommand();
+ ~ClearCommand() override;
+};
+
+class ClearColorCommand : public Command {
+ public:
+ ClearColorCommand();
+ ~ClearColorCommand() override;
+
+ void SetR(float r) { r_ = r; }
+ float GetR() const { return r_; }
+
+ void SetG(float g) { g_ = g; }
+ float GetG() const { return g_; }
+
+ void SetB(float b) { b_ = b; }
+ float GetB() const { return b_; }
+
+ void SetA(float a) { a_ = a; }
+ float GetA() const { return a_; }
+
+ private:
+ float r_ = 0.0;
+ float g_ = 0.0;
+ float b_ = 0.0;
+ float a_ = 0.0;
+};
+
+class ClearDepthCommand : public Command {
+ public:
+ ClearDepthCommand();
+ ~ClearDepthCommand() override;
+
+ void SetValue(float val) { value_ = val; }
+ float GetValue() const { return value_; }
+
+ private:
+ float value_ = 0.0;
+};
+
+class ClearStencilCommand : public Command {
+ public:
+ ClearStencilCommand();
+ ~ClearStencilCommand() override;
+
+ void SetValue(uint32_t val) { value_ = val; }
+ uint32_t GetValue() const { return value_; }
+
+ private:
+ uint32_t value_ = 0;
+};
+
+class PatchParameterVerticesCommand : public Command {
+ public:
+ PatchParameterVerticesCommand();
+ ~PatchParameterVerticesCommand() override;
+
+ void SetControlPointCount(uint32_t count) { control_point_count_ = count; }
+ uint32_t GetControlPointCount() const { return control_point_count_; }
+
+ private:
+ uint32_t control_point_count_ = 0;
+};
+
+class EntryPointCommand : public Command {
+ public:
+ EntryPointCommand();
+ ~EntryPointCommand() override;
+
+ void SetShaderType(ShaderType type) { shader_type_ = type; }
+ ShaderType GetShaderType() const { return shader_type_; }
+
+ void SetEntryPointName(const std::string& name) { entry_point_name_ = name; }
+ std::string GetEntryPointName() const { return entry_point_name_; }
+
+ private:
+ ShaderType shader_type_ = ShaderType::kVertex;
+ std::string entry_point_name_;
+};
+
+} // namespace amber
+
+#endif // SRC_COMMAND_H_
diff --git a/src/command_data.cc b/src/command_data.cc
new file mode 100644
index 0000000..6037907
--- /dev/null
+++ b/src/command_data.cc
@@ -0,0 +1,57 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/command_data.h"
+
+namespace amber {
+
+Topology NameToTopology(const std::string& name) {
+ const static struct {
+ const char* name;
+ Topology val;
+ } topologies[] = {
+ {"PATCH_LIST", Topology::kPatchList},
+ {"POINT_LIST", Topology::kPointList},
+ {"GL_LINE_STRIP_ADJACENCY", Topology::kLineStripWithAdjacency},
+ {"GL_LINE_STRIP", Topology::kLineStrip},
+ {"GL_LINES", Topology::kLineList},
+ {"GL_LINES_ADJACENCY", Topology::kLineListWithAdjacency},
+ {"GL_PATCHES", Topology::kPatchList},
+ {"GL_POINTS", Topology::kPointList},
+ {"GL_TRIANGLE_STRIP", Topology::kTriangleStrip},
+ {"GL_TRIANGLE_FAN", Topology::kTriangleFan},
+ {"GL_TRIANGLES", Topology::kTriangleList},
+ {"GL_TRIANGLES_ADJACENCY", Topology::kTriangleListWithAdjacency},
+ {"GL_TRIANGLE_STRIP_ADJACENCY", Topology::kTriangleStripWithAdjacency},
+ {"LINE_LIST", Topology::kLineList},
+ {"LINE_LIST_WITH_ADJACENCY", Topology::kLineListWithAdjacency},
+ {"LINE_STRIP", Topology::kLineStrip},
+ {"LINE_STRIP_WITH_ADJACENCY", Topology::kLineStripWithAdjacency},
+ {"TRIANGLE_FAN", Topology::kTriangleFan},
+ {"TRIANGLE_LIST", Topology::kTriangleList},
+ {"TRIANGLE_LIST_WITH_ADJACENCY", Topology::kTriangleListWithAdjacency},
+ {"TRIANGLE_STRIP", Topology::kTriangleStrip},
+ {"TRIANGLE_STRIP_WITH_ADJACENCY", Topology::kTriangleStripWithAdjacency},
+ };
+
+ // TODO(dsinclair): Make smarter if needed
+ for (auto& topo : topologies) {
+ if (topo.name == name)
+ return topo.val;
+ }
+
+ return Topology::kUnknown;
+}
+
+} // namespace amber
diff --git a/src/command_data.h b/src/command_data.h
new file mode 100644
index 0000000..430533c
--- /dev/null
+++ b/src/command_data.h
@@ -0,0 +1,184 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_COMMAND_DATA_H_
+#define SRC_COMMAND_DATA_H_
+
+#include <cstdint>
+#include <string>
+
+namespace amber {
+
+enum class Topology : uint8_t {
+ kUnknown = 0,
+ kPointList,
+ kLineList,
+ kLineStrip,
+ kTriangleList,
+ kTriangleStrip,
+ kTriangleFan,
+ kLineListWithAdjacency,
+ kLineStripWithAdjacency,
+ kTriangleListWithAdjacency,
+ kTriangleStripWithAdjacency,
+ kPatchList,
+};
+
+enum class PolygonMode : uint8_t {
+ kFill = 0,
+ kLine,
+ kPoint,
+};
+
+enum class CullMode : uint8_t {
+ kNone = 0,
+ kFront,
+ kBack,
+ kFrontAndBack,
+};
+
+enum class FrontFace : uint8_t {
+ kCounterClockwise = 0,
+ kClockwise,
+};
+
+enum ColorMask {
+ kColorMaskR = 1 << 0,
+ kColorMaskG = 1 << 1,
+ kColorMaskB = 1 << 2,
+ kColorMaskA = 1 << 3,
+};
+
+enum class CompareOp : uint8_t {
+ kNever = 0,
+ kLess,
+ kEqual,
+ kLessOrEqual,
+ kGreater,
+ kNotEqual,
+ kGreaterOrEqual,
+ kAlways,
+};
+
+enum class StencilOp : uint8_t {
+ kKeep = 0,
+ kZero,
+ kReplace,
+ kIncrementAndClamp,
+ kDecrementAndClamp,
+ kInvert,
+ kIncrementAndWrap,
+ kDecrementAndWrap,
+};
+
+enum class LogicOp : uint8_t {
+ kClear = 0,
+ kAnd,
+ kAndReverse,
+ kCopy,
+ kAndInverted,
+ kNoOp,
+ kXor,
+ kOr,
+ kNor,
+ kEquivalent,
+ kInvert,
+ kOrReverse,
+ kCopyInverted,
+ kOrInverted,
+ kNand,
+ kSet,
+};
+
+enum class BlendOp : uint8_t {
+ kAdd = 0,
+ kSubtract,
+ kReverseSubtract,
+ kMin,
+ kMax,
+ kZero,
+ kSrc,
+ kDst,
+ kSrcOver,
+ kDstOver,
+ kSrcIn,
+ kDstIn,
+ kSrcOut,
+ kDstOut,
+ kSrcAtop,
+ kDstAtop,
+ kXor,
+ kMultiply,
+ kScreen,
+ kOverlay,
+ kDarken,
+ kLighten,
+ kColorDodge,
+ kColorBurn,
+ kHardLight,
+ kSoftLight,
+ kDifference,
+ kExclusion,
+ kInvert,
+ kInvertRGB,
+ kLinearDodge,
+ kLinearBurn,
+ kVividLight,
+ kLinearLight,
+ kPinLight,
+ kHardMix,
+ kHslHue,
+ kHslSaturation,
+ kHslColor,
+ kHslLuminosity,
+ kPlus,
+ kPlusClamped,
+ kPlusClampedAlpha,
+ kPlusDarker,
+ kMinus,
+ kMinusClamped,
+ kContrast,
+ kInvertOvg,
+ kRed,
+ kGreen,
+ kBlue,
+};
+
+enum class BlendFactor : uint8_t {
+ kZero = 0,
+ kOne,
+ kSrcColor,
+ kOneMinusSrcColor,
+ kDstColor,
+ kOneMinusDstColor,
+ kSrcAlpha,
+ kOneMinusSrcAlpha,
+ kDstAlpha,
+ kOneMinusDstAlpha,
+ kConstantColor,
+ kOneMinusConstantColor,
+ kConstantAlpha,
+ kOneMinusConstantAlpha,
+ kSrcAlphaSaturate,
+ kSrc1Color,
+ kOneMinusSrc1Color,
+ kSrc1Alpha,
+ kOneMinusSrc1Alpha,
+};
+
+Topology NameToTopology(const std::string& name);
+
+} // namespace amber
+
+#endif // SRC_COMMAND_DATA_H_
diff --git a/src/command_data_test.cc b/src/command_data_test.cc
new file mode 100644
index 0000000..c465a17
--- /dev/null
+++ b/src/command_data_test.cc
@@ -0,0 +1,61 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/command_data.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+
+using CommandDataTest = testing::Test;
+
+TEST_F(CommandDataTest, TopologyConversions) {
+ const struct {
+ const char* name;
+ Topology val;
+ } topologies[] = {
+ {"PATCH_LIST", Topology::kPatchList},
+ {"POINT_LIST", Topology::kPointList},
+ {"GL_LINE_STRIP_ADJACENCY", Topology::kLineStripWithAdjacency},
+ {"GL_LINE_STRIP", Topology::kLineStrip},
+ {"GL_LINES", Topology::kLineList},
+ {"GL_LINES_ADJACENCY", Topology::kLineListWithAdjacency},
+ {"GL_PATCHES", Topology::kPatchList},
+ {"GL_POINTS", Topology::kPointList},
+ {"GL_TRIANGLE_STRIP", Topology::kTriangleStrip},
+ {"GL_TRIANGLE_FAN", Topology::kTriangleFan},
+ {"GL_TRIANGLES", Topology::kTriangleList},
+ {"GL_TRIANGLES_ADJACENCY", Topology::kTriangleListWithAdjacency},
+ {"GL_TRIANGLE_STRIP_ADJACENCY", Topology::kTriangleStripWithAdjacency},
+ {"LINE_LIST", Topology::kLineList},
+ {"LINE_LIST_WITH_ADJACENCY", Topology::kLineListWithAdjacency},
+ {"LINE_STRIP", Topology::kLineStrip},
+ {"LINE_STRIP_WITH_ADJACENCY", Topology::kLineStripWithAdjacency},
+ {"TRIANGLE_FAN", Topology::kTriangleFan},
+ {"TRIANGLE_LIST", Topology::kTriangleList},
+ {"TRIANGLE_LIST_WITH_ADJACENCY", Topology::kTriangleListWithAdjacency},
+ {"TRIANGLE_STRIP", Topology::kTriangleStrip},
+ {"TRIANGLE_STRIP_WITH_ADJACENCY", Topology::kTriangleStripWithAdjacency},
+ };
+
+ for (const auto& topo : topologies) {
+ EXPECT_EQ(topo.val, NameToTopology(topo.name));
+ }
+}
+
+TEST_F(CommandDataTest, TopologyInvalid) {
+ EXPECT_EQ(Topology::kUnknown, NameToTopology(""));
+ EXPECT_EQ(Topology::kUnknown, NameToTopology("Invalid_Topology"));
+}
+
+} // namespace amber
diff --git a/src/datum_type.cc b/src/datum_type.cc
new file mode 100644
index 0000000..431bb72
--- /dev/null
+++ b/src/datum_type.cc
@@ -0,0 +1,24 @@
+// Copyright 2018 The Amber 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.
+#include "src/datum_type.h"
+
+namespace amber {
+
+DatumType::DatumType() = default;
+
+DatumType::~DatumType() = default;
+
+DatumType& DatumType::operator=(const DatumType&) = default;
+
+} // namespace amber
diff --git a/src/datum_type.h b/src/datum_type.h
new file mode 100644
index 0000000..b6c5a6c
--- /dev/null
+++ b/src/datum_type.h
@@ -0,0 +1,70 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_DATUM_TYPE_H_
+#define SRC_DATUM_TYPE_H_
+
+#include <cstdint>
+
+namespace amber {
+
+enum class DataType {
+ kInt8 = 0,
+ kInt16,
+ kInt32,
+ kInt64,
+ kUint8,
+ kUint16,
+ kUint32,
+ kUint64,
+ kFloat,
+ kDouble,
+};
+
+class DatumType {
+ public:
+ DatumType();
+ ~DatumType();
+
+ DatumType& operator=(const DatumType&);
+
+ bool IsInt8() const { return type_ == DataType::kInt8; }
+ bool IsInt16() const { return type_ == DataType::kInt16; }
+ bool IsInt32() const { return type_ == DataType::kInt32; }
+ bool IsInt64() const { return type_ == DataType::kInt64; }
+ bool IsUint8() const { return type_ == DataType::kUint8; }
+ bool IsUint16() const { return type_ == DataType::kUint16; }
+ bool IsUint32() const { return type_ == DataType::kUint32; }
+ bool IsUint64() const { return type_ == DataType::kUint64; }
+ bool IsFloat() const { return type_ == DataType::kFloat; }
+ bool IsDouble() const { return type_ == DataType::kDouble; }
+
+ void SetType(DataType type) { type_ = type; }
+ DataType GetType() const { return type_; }
+
+ void SetColumnCount(uint32_t count) { column_count_ = count; }
+ uint32_t ColumnCount() const { return column_count_; }
+
+ void SetRowCount(uint32_t count) { row_count_ = count; }
+ uint32_t RowCount() const { return row_count_; }
+
+ private:
+ DataType type_ = DataType::kUint8;
+ uint32_t column_count_ = 1;
+ uint32_t row_count_ = 1;
+};
+
+} // namespace amber
+
+#endif // SRC_DATUM_TYPE_H_
diff --git a/src/dawn/find_dawn.cmake b/src/dawn/find_dawn.cmake
new file mode 100644
index 0000000..aed0ebc
--- /dev/null
+++ b/src/dawn/find_dawn.cmake
@@ -0,0 +1,68 @@
+# Copyright 2018 The Amber 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.
+
+
+# Include this file to find Dawn and and set up compilation and linking.
+
+# Exports these settings to the includer:
+# Boolean Dawn_FOUND indicates whether we found Dawn.
+# If Dawn was found, then library dependency Dawn::dawn_native will be set up.
+set(Dawn_FOUND FALSE)
+
+# Setup via CMake setting variables:
+#
+# Separately specify the directory locations of the Dawn headers and
+# the dawn_native library.
+#
+# -DDawn_INCLUDE_DIR=<directory containing dawn/dawn_export.h>
+# -DDawn_GEN_INCLUDE_DIR=<directory containing dawn/dawn.h>
+# -DDawn_LIBRARY_DIR=<directory containing dawn_native>
+
+
+find_path(Dawn_INCLUDE_DIR
+ NAMES dawn/dawn_export.h
+ PATHS
+ "${Dawn_INCLUDE_DIR}"
+ )
+find_path(Dawn_GEN_INCLUDE_DIR
+ NAMES dawn/dawn.h dawn/dawncpp.h
+ PATHS
+ "${Dawn_GEN_INCLUDE_DIR}"
+ )
+find_library(Dawn_LIBRARY
+ NAMES dawn_native
+ PATHS
+ "${Dawn_LIBRARY_DIR}"
+ )
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Dawn
+ DEFAULT_MSG
+ Dawn_INCLUDE_DIR Dawn_GEN_INCLUDE_DIR Dawn_LIBRARY)
+
+if(${Dawn_FOUND} AND NOT TARGET Dawn::dawn_native)
+ add_library(Dawn::dawn_native UNKNOWN IMPORTED)
+ set_target_properties(Dawn::dawn_native PROPERTIES
+ IMPORTED_LOCATION "${Dawn_LIBRARY}"
+ INTERFACE_INCLUDE_DIRECTORIES "${Dawn_INCLUDE_DIR}"
+ INTERFACE_INCLUDE_DIRECTORIES "${Dawn_GEN_INCLUDE_DIR}")
+endif()
+
+if (${Dawn_FOUND})
+ message(STATUS "Amber: Using Dawn headers at ${Dawn_INCLUDE_DIR}")
+ message(STATUS "Amber: Using Dawn generated headers at ${Dawn_GEN_INCLUDE_DIR}")
+ message(STATUS "Amber: Using Dawn library ${Dawn_LIBRARY}")
+else()
+ message(STATUS "Amber: Did not find Dawn")
+endif()
diff --git a/src/engine.cc b/src/engine.cc
new file mode 100644
index 0000000..ff90a92
--- /dev/null
+++ b/src/engine.cc
@@ -0,0 +1,45 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/engine.h"
+
+#include "src/make_unique.h"
+
+#if AMBER_ENGINE_VULKAN
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
+#include "src/vulkan/engine_vulkan.h"
+#pragma clang diagnostic pop
+#endif // AMBER_ENGINE_VULKAN
+
+namespace amber {
+
+// static
+std::unique_ptr<Engine> Engine::Create(EngineType type) {
+ std::unique_ptr<Engine> engine;
+ switch (type) {
+ case EngineType::kVulkan:
+#if AMBER_ENGINE_VULKAN
+ engine = MakeUnique<vulkan::EngineVulkan>();
+#endif // AMBER_ENGINE_VULKAN
+ break;
+ }
+ return engine;
+}
+
+Engine::Engine() = default;
+
+Engine::~Engine() = default;
+
+} // namespace amber
diff --git a/src/engine.h b/src/engine.h
new file mode 100644
index 0000000..a58a41e
--- /dev/null
+++ b/src/engine.h
@@ -0,0 +1,115 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef ENGINE_H_
+#define ENGINE_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/amber.h"
+#include "amber/result.h"
+#include "src/buffer_data.h"
+#include "src/command.h"
+#include "src/feature.h"
+#include "src/format.h"
+#include "src/shader_data.h"
+
+namespace amber {
+
+enum class PipelineType : uint8_t {
+ kCompute = 0,
+ kGraphics,
+};
+
+class Engine {
+ public:
+ static std::unique_ptr<Engine> Create(EngineType type);
+
+ virtual ~Engine();
+
+ // Initialize the engine.
+ virtual Result Initialize() = 0;
+
+ // Initialize the engine with the provided device. The device is _not_ owned
+ // by the engine and should not be destroyed.
+ virtual Result InitializeWithDevice(void* default_device) = 0;
+
+ // Shutdown the engine and cleanup any resources.
+ virtual Result Shutdown() = 0;
+
+ // Enable |feature|. If the feature requires a pixel format it will be
+ // provided in |format|, otherwise |format| is a nullptr.
+ virtual Result AddRequirement(Feature feature, const Format* format) = 0;
+
+ // Create graphics pipeline.
+ virtual Result CreatePipeline(PipelineType type) = 0;
+
+ // Set the shader of |type| to the binary |data|.
+ virtual Result SetShader(ShaderType type,
+ const std::vector<uint32_t>& data) = 0;
+
+ // Provides the data for a given buffer to be bound at the given location.
+ virtual Result SetBuffer(BufferType type,
+ uint8_t location,
+ const Format& format,
+ const std::vector<Value>& data) = 0;
+
+ // Execute the clear color command
+ virtual Result ExecuteClearColor(const ClearColorCommand* cmd) = 0;
+
+ // Execute the clear stencil command
+ virtual Result ExecuteClearStencil(const ClearStencilCommand* cmd) = 0;
+
+ // Execute the clear depth command
+ virtual Result ExecuteClearDepth(const ClearDepthCommand* cmd) = 0;
+
+ // Execute the clear command
+ virtual Result ExecuteClear(const ClearCommand* cmd) = 0;
+
+ // Execute the draw rect command
+ virtual Result ExecuteDrawRect(const DrawRectCommand* cmd) = 0;
+
+ // Execute the draw arrays command
+ virtual Result ExecuteDrawArrays(const DrawArraysCommand* cmd) = 0;
+
+ // Execute the compute command
+ virtual Result ExecuteCompute(const ComputeCommand* cmd) = 0;
+
+ // Execute the entry point command
+ virtual Result ExecuteEntryPoint(const EntryPointCommand* cmd) = 0;
+
+ // Execute the patch command
+ virtual Result ExecutePatchParameterVertices(
+ const PatchParameterVerticesCommand* cmd) = 0;
+
+ // Execute the probe command
+ virtual Result ExecuteProbe(const ProbeCommand* cmd) = 0;
+
+ // Execute the probe ssbo command
+ virtual Result ExecuteProbeSSBO(const ProbeSSBOCommand* cmd) = 0;
+
+ // Execute the buffer command
+ virtual Result ExecuteBuffer(const BufferCommand* cmd) = 0;
+
+ // Execute the tolerance command
+ virtual Result ExecuteTolerance(const ToleranceCommand* cmd) = 0;
+
+ protected:
+ Engine();
+};
+
+} // namespace amber
+
+#endif // ENGINE_H_
diff --git a/src/executor.cc b/src/executor.cc
new file mode 100644
index 0000000..249a89f
--- /dev/null
+++ b/src/executor.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/executor.h"
+
+namespace amber {
+
+Executor::Executor() = default;
+
+Executor::~Executor() = default;
+
+} // namespace amber
diff --git a/src/executor.h b/src/executor.h
new file mode 100644
index 0000000..a73d5ee
--- /dev/null
+++ b/src/executor.h
@@ -0,0 +1,36 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_EXECUTOR_H_
+#define SRC_EXECUTOR_H_
+
+#include "amber/result.h"
+#include "src/engine.h"
+#include "src/script.h"
+
+namespace amber {
+
+class Executor {
+ public:
+ virtual ~Executor();
+
+ virtual Result Execute(Engine*, const Script*) = 0;
+
+ protected:
+ Executor();
+};
+
+} // namespace amber
+
+#endif // SRC_EXECUTOR_H_
diff --git a/src/feature.h b/src/feature.h
new file mode 100644
index 0000000..d6c030c
--- /dev/null
+++ b/src/feature.h
@@ -0,0 +1,83 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_FEATURE_H_
+#define SRC_FEATURE_H_
+
+namespace amber {
+
+enum class Feature {
+ kUnknown = 0,
+ kRobustBufferAccess,
+ kFullDrawIndexUint32,
+ kImageCubeArray,
+ kIndependentBlend,
+ kGeometryShader,
+ kTessellationShader,
+ kSampleRateShading,
+ kDualSrcBlend,
+ kLogicOp,
+ kMultiDrawIndirect,
+ kDrawIndirectFirstInstance,
+ kDepthClamp,
+ kDepthBiasClamp,
+ kFillModeNonSolid,
+ kDepthBounds,
+ kWideLines,
+ kLargePoints,
+ kAlphaToOne,
+ kMultiViewport,
+ kSamplerAnisotropy,
+ kTextureCompressionETC2,
+ kTextureCompressionASTC_LDR,
+ kTextureCompressionBC,
+ kOcclusionQueryPrecise,
+ kPipelineStatisticsQuery,
+ kVertexPipelineStoresAndAtomics,
+ kFragmentStoresAndAtomics,
+ kShaderTessellationAndGeometryPointSize,
+ kShaderImageGatherExtended,
+ kShaderStorageImageExtendedFormats,
+ kShaderStorageImageMultisample,
+ kShaderStorageImageReadWithoutFormat,
+ kShaderStorageImageWriteWithoutFormat,
+ kShaderUniformBufferArrayDynamicIndexing,
+ kShaderSampledImageArrayDynamicIndexing,
+ kShaderStorageBufferArrayDynamicIndexing,
+ kShaderStorageImageArrayDynamicIndexing,
+ kShaderClipDistance,
+ kShaderCullDistance,
+ kShaderFloat64,
+ kShaderInt64,
+ kShaderInt16,
+ kShaderResourceResidency,
+ kShaderResourceMinLod,
+ kSparseBinding,
+ kSparseResidencyBuffer,
+ kSparseResidencyImage2D,
+ kSparseResidencyImage3D,
+ kSparseResidency2Samples,
+ kSparseResidency4Samples,
+ kSparseResidency8Samples,
+ kSparseResidency16Samples,
+ kSparseResidencyAliased,
+ kVariableMultisampleRate,
+ kInheritedQueries,
+ kFramebuffer,
+ kDepthStencil,
+};
+
+} // namespace amber
+
+#endif // SRC_FEATURE_H_
diff --git a/src/format.cc b/src/format.cc
new file mode 100644
index 0000000..0881379
--- /dev/null
+++ b/src/format.cc
@@ -0,0 +1,25 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/format.h"
+
+namespace amber {
+
+Format::Format() = default;
+
+Format::Format(const Format&) = default;
+
+Format::~Format() = default;
+
+} // namespace amber
diff --git a/src/format.h b/src/format.h
new file mode 100644
index 0000000..fd900a8
--- /dev/null
+++ b/src/format.h
@@ -0,0 +1,67 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_FORMAT_H_
+#define SRC_FORMAT_H_
+
+#include <cstdint>
+#include <vector>
+
+#include "src/format_data.h"
+
+namespace amber {
+
+class Format {
+ public:
+ struct Component {
+ Component(FormatComponentType t, FormatMode m, uint8_t bits)
+ : type(t), mode(m), num_bits(bits) {}
+
+ FormatComponentType type;
+ FormatMode mode;
+ uint8_t num_bits;
+ };
+
+ Format();
+ Format(const Format&);
+ ~Format();
+
+ void SetFormatType(FormatType type) { type_ = type; }
+ FormatType GetFormatType() const { return type_; }
+
+ void SetPackSize(uint8_t size) { pack_size_ = size; }
+ uint8_t GetPackSize() const { return pack_size_; }
+
+ void AddComponent(FormatComponentType type, FormatMode mode, uint8_t size) {
+ components_.emplace_back(type, mode, size);
+ }
+ const std::vector<Component>& GetComponents() const { return components_; }
+
+ uint32_t GetByteSize() const {
+ uint32_t bits = 0;
+ for (uint32_t j = 0; j < components_.size(); ++j) {
+ bits += components_[j].num_bits;
+ }
+ return bits / 8;
+ }
+
+ private:
+ FormatType type_;
+ uint8_t pack_size_ = 0;
+ std::vector<Component> components_;
+};
+
+} // namespace amber
+
+#endif // SRC_FORMAT_H_
diff --git a/src/format_data.h b/src/format_data.h
new file mode 100644
index 0000000..98237bf
--- /dev/null
+++ b/src/format_data.h
@@ -0,0 +1,173 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_FORMAT_DATA_H_
+#define SRC_FORMAT_DATA_H_
+
+enum class FormatComponentType {
+ kA = 0,
+ kR,
+ kG,
+ kB,
+ kX,
+ kD,
+ kS,
+};
+
+enum class FormatMode {
+ kUNorm = 0,
+ kUInt,
+ kUFloat,
+ kUScaled,
+ kSInt,
+ kSNorm,
+ kSScaled,
+ kSFloat,
+ kSRGB,
+};
+
+enum class FormatType {
+ kUnknown = 0,
+ kA1R5G5B5_UNORM_PACK16,
+ kA2B10G10R10_SINT_PACK32,
+ kA2B10G10R10_SNORM_PACK32,
+ kA2B10G10R10_SSCALED_PACK32,
+ kA2B10G10R10_UINT_PACK32,
+ kA2B10G10R10_UNORM_PACK32,
+ kA2B10G10R10_USCALED_PACK32,
+ kA2R10G10B10_SINT_PACK32,
+ kA2R10G10B10_SNORM_PACK32,
+ kA2R10G10B10_SSCALED_PACK32,
+ kA2R10G10B10_UINT_PACK32,
+ kA2R10G10B10_UNORM_PACK32,
+ kA2R10G10B10_USCALED_PACK32,
+ kA8B8G8R8_SINT_PACK32,
+ kA8B8G8R8_SNORM_PACK32,
+ kA8B8G8R8_SRGB_PACK32,
+ kA8B8G8R8_SSCALED_PACK32,
+ kA8B8G8R8_UINT_PACK32,
+ kA8B8G8R8_UNORM_PACK32,
+ kA8B8G8R8_USCALED_PACK32,
+ kB10G11R11_UFLOAT_PACK32,
+ kB4G4R4A4_UNORM_PACK16,
+ kB5G5R5A1_UNORM_PACK16,
+ kB5G6R5_UNORM_PACK16,
+ kB8G8R8A8_SINT,
+ kB8G8R8A8_SNORM,
+ kB8G8R8A8_SRGB,
+ kB8G8R8A8_SSCALED,
+ kB8G8R8A8_UINT,
+ kB8G8R8A8_UNORM,
+ kB8G8R8A8_USCALED,
+ kB8G8R8_SINT,
+ kB8G8R8_SNORM,
+ kB8G8R8_SRGB,
+ kB8G8R8_SSCALED,
+ kB8G8R8_UINT,
+ kB8G8R8_UNORM,
+ kB8G8R8_USCALED,
+ kD16_UNORM,
+ kD16_UNORM_S8_UINT,
+ kD24_UNORM_S8_UINT,
+ kD32_SFLOAT,
+ kD32_SFLOAT_S8_UINT,
+ kR16G16B16A16_SFLOAT,
+ kR16G16B16A16_SINT,
+ kR16G16B16A16_SNORM,
+ kR16G16B16A16_SSCALED,
+ kR16G16B16A16_UINT,
+ kR16G16B16A16_UNORM,
+ kR16G16B16A16_USCALED,
+ kR16G16B16_SFLOAT,
+ kR16G16B16_SINT,
+ kR16G16B16_SNORM,
+ kR16G16B16_SSCALED,
+ kR16G16B16_UINT,
+ kR16G16B16_UNORM,
+ kR16G16B16_USCALED,
+ kR16G16_SFLOAT,
+ kR16G16_SINT,
+ kR16G16_SNORM,
+ kR16G16_SSCALED,
+ kR16G16_UINT,
+ kR16G16_UNORM,
+ kR16G16_USCALED,
+ kR16_SFLOAT,
+ kR16_SINT,
+ kR16_SNORM,
+ kR16_SSCALED,
+ kR16_UINT,
+ kR16_UNORM,
+ kR16_USCALED,
+ kR32G32B32A32_SFLOAT,
+ kR32G32B32A32_SINT,
+ kR32G32B32A32_UINT,
+ kR32G32B32_SFLOAT,
+ kR32G32B32_SINT,
+ kR32G32B32_UINT,
+ kR32G32_SFLOAT,
+ kR32G32_SINT,
+ kR32G32_UINT,
+ kR32_SFLOAT,
+ kR32_SINT,
+ kR32_UINT,
+ kR4G4B4A4_UNORM_PACK16,
+ kR4G4_UNORM_PACK8,
+ kR5G5B5A1_UNORM_PACK16,
+ kR5G6B5_UNORM_PACK16,
+ kR64G64B64A64_SFLOAT,
+ kR64G64B64A64_SINT,
+ kR64G64B64A64_UINT,
+ kR64G64B64_SFLOAT,
+ kR64G64B64_SINT,
+ kR64G64B64_UINT,
+ kR64G64_SFLOAT,
+ kR64G64_SINT,
+ kR64G64_UINT,
+ kR64_SFLOAT,
+ kR64_SINT,
+ kR64_UINT,
+ kR8G8B8A8_SINT,
+ kR8G8B8A8_SNORM,
+ kR8G8B8A8_SRGB,
+ kR8G8B8A8_SSCALED,
+ kR8G8B8A8_UINT,
+ kR8G8B8A8_UNORM,
+ kR8G8B8A8_USCALED,
+ kR8G8B8_SINT,
+ kR8G8B8_SNORM,
+ kR8G8B8_SRGB,
+ kR8G8B8_SSCALED,
+ kR8G8B8_UINT,
+ kR8G8B8_UNORM,
+ kR8G8B8_USCALED,
+ kR8G8_SINT,
+ kR8G8_SNORM,
+ kR8G8_SRGB,
+ kR8G8_SSCALED,
+ kR8G8_UINT,
+ kR8G8_UNORM,
+ kR8G8_USCALED,
+ kR8_SINT,
+ kR8_SNORM,
+ kR8_SRGB,
+ kR8_SSCALED,
+ kR8_UINT,
+ kR8_UNORM,
+ kR8_USCALED,
+ kS8_UINT,
+ kX8_D24_UNORM_PACK32,
+};
+
+#endif // SRC_FORMAT_DATA_H_
diff --git a/src/make_unique.h b/src/make_unique.h
new file mode 100644
index 0000000..dff615d
--- /dev/null
+++ b/src/make_unique.h
@@ -0,0 +1,30 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_MAKE_UNIQUE_H_
+#define SRC_MAKE_UNIQUE_H_
+
+#include <memory>
+#include <utility>
+
+namespace amber {
+
+template <typename T, typename... Args>
+std::unique_ptr<T> MakeUnique(Args&&... args) {
+ return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+}
+
+} // namespace amber
+
+#endif // SRC_MAKE_UNIQUE_H_
diff --git a/src/parser.cc b/src/parser.cc
new file mode 100644
index 0000000..33d3d05
--- /dev/null
+++ b/src/parser.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/parser.h"
+
+namespace amber {
+
+Parser::Parser() = default;
+
+Parser::~Parser() = default;
+
+} // namespace amber
diff --git a/src/parser.h b/src/parser.h
new file mode 100644
index 0000000..d388f3b
--- /dev/null
+++ b/src/parser.h
@@ -0,0 +1,38 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_PARSER_H_
+#define SRC_PARSER_H_
+
+#include <string>
+
+#include "amber/result.h"
+#include "src/script.h"
+
+namespace amber {
+
+class Parser {
+ public:
+ virtual ~Parser();
+
+ virtual Result Parse(const std::string& data) = 0;
+ virtual const Script* GetScript() const = 0;
+
+ protected:
+ Parser();
+};
+
+} // namespace amber
+
+#endif // SRC_PARSER_H_
diff --git a/src/pipeline_data.cc b/src/pipeline_data.cc
new file mode 100644
index 0000000..89f0113
--- /dev/null
+++ b/src/pipeline_data.cc
@@ -0,0 +1,25 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/pipeline_data.h"
+
+namespace amber {
+
+PipelineData::PipelineData() = default;
+
+PipelineData::~PipelineData() = default;
+
+PipelineData::PipelineData(const PipelineData&) = default;
+
+} // namespace amber
diff --git a/src/pipeline_data.h b/src/pipeline_data.h
new file mode 100644
index 0000000..6e5b9da
--- /dev/null
+++ b/src/pipeline_data.h
@@ -0,0 +1,217 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_PIPELINE_H_
+#define SRC_PIPELINE_H_
+
+#include <limits>
+
+#include "src/command_data.h"
+
+namespace amber {
+
+class PipelineData {
+ public:
+ PipelineData();
+ ~PipelineData();
+ PipelineData(const PipelineData&);
+
+ void SetTopology(Topology topo) { topology_ = topo; }
+ Topology GetTopology() const { return topology_; }
+
+ void SetPolygonMode(PolygonMode mode) { polygon_mode_ = mode; }
+ PolygonMode GetPolygonMode() const { return polygon_mode_; }
+
+ void SetCullMode(CullMode mode) { cull_mode_ = mode; }
+ CullMode GetCullMode() const { return cull_mode_; }
+
+ void SetFrontFace(FrontFace face) { front_face_ = face; }
+ FrontFace GetFrontFace() const { return front_face_; }
+
+ void SetDepthCompareOp(CompareOp op) { depth_compare_op_ = op; }
+ CompareOp GetDepthCompareOp() const { return depth_compare_op_; }
+
+ void SetColorWriteMask(uint8_t mask) { color_write_mask_ = mask; }
+ uint8_t GetColorWriteMask() const { return color_write_mask_; }
+
+ void SetFrontFailOp(StencilOp op) { front_fail_op_ = op; }
+ StencilOp GetFrontFailOp() const { return front_fail_op_; }
+
+ void SetFrontPassOp(StencilOp op) { front_pass_op_ = op; }
+ StencilOp GetFrontPassOp() const { return front_pass_op_; }
+
+ void SetFrontDepthFailOp(StencilOp op) { front_depth_fail_op_ = op; }
+ StencilOp GetFrontDepthFailOp() const { return front_depth_fail_op_; }
+
+ void SetFrontCompareOp(CompareOp op) { front_compare_op_ = op; }
+ CompareOp GetFrontCompareOp() const { return front_compare_op_; }
+
+ void SetFrontCompareMask(uint32_t mask) { front_compare_mask_ = mask; }
+ uint32_t GetFrontCompareMask() const { return front_compare_mask_; }
+
+ void SetFrontWriteMask(uint32_t mask) { front_write_mask_ = mask; }
+ uint32_t GetFrontWriteMask() const { return front_write_mask_; }
+
+ void SetFrontReference(uint32_t ref) { front_reference_ = ref; }
+ uint32_t GetFrontReference() const { return front_reference_; }
+
+ void SetBackFailOp(StencilOp op) { back_fail_op_ = op; }
+ StencilOp GetBackFailOp() const { return back_fail_op_; }
+
+ void SetBackPassOp(StencilOp op) { back_pass_op_ = op; }
+ StencilOp GetBackPassOp() const { return back_pass_op_; }
+
+ void SetBackDepthFailOp(StencilOp op) { back_depth_fail_op_ = op; }
+ StencilOp GetBackDepthFailOp() const { return back_depth_fail_op_; }
+
+ void SetBackCompareOp(CompareOp op) { back_compare_op_ = op; }
+ CompareOp GetBackCompareOp() const { return back_compare_op_; }
+
+ void SetBackCompareMask(uint32_t mask) { back_compare_mask_ = mask; }
+ uint32_t GetBackCompareMask() const { return back_compare_mask_; }
+
+ void SetBackWriteMask(uint32_t mask) { back_write_mask_ = mask; }
+ uint32_t GetbackWriteMask() const { return back_write_mask_; }
+
+ void SetBackReference(uint32_t ref) { back_reference_ = ref; }
+ uint32_t GetBackReference() const { return back_reference_; }
+
+ void SetLineWidth(float width) { line_width_ = width; }
+ float GetLineWidth() const { return line_width_; }
+
+ void SetEnableBlend(bool v) { enable_blend_ = v; }
+ bool GetEnableBlend() const { return enable_blend_; }
+
+ void SetEnableDepthTest(bool v) { enable_depth_test_ = v; }
+ bool GetEnableDepthTest() const { return enable_depth_test_; }
+
+ void SetEnableDepthWrite(bool v) { enable_depth_write_ = v; }
+ bool GetEnableDepthWrite() const { return enable_depth_write_; }
+
+ void SetEnableStencilTest(bool v) { enable_stencil_test_ = v; }
+ bool GetEnableStencilTest() const { return enable_stencil_test_; }
+
+ void SetEnablePrimitiveRestart(bool v) { enable_primitive_restart_ = v; }
+ bool GetEnablePrimitiveRestart() const { return enable_primitive_restart_; }
+
+ void SetEnableDepthClamp(bool v) { enable_depth_clamp_ = v; }
+ bool GetEnableDepthClamp() const { return enable_depth_clamp_; }
+
+ void SetEnableRasterizerDiscard(bool v) { enable_rasterizer_discard_ = v; }
+ bool GetEnableRasterizerDiscard() const { return enable_rasterizer_discard_; }
+
+ void SetEnableDepthBias(bool v) { enable_depth_bias_ = v; }
+ bool GetEnableDepthBias() const { return enable_depth_bias_; }
+
+ void SetEnableLogicOp(bool v) { enable_logic_op_ = v; }
+ bool GetEnableLogicOp() const { return enable_logic_op_; }
+
+ void SetEnableDepthBoundsTest(bool v) { enable_depth_bounds_test_ = v; }
+ bool GetEnableDepthBoundsTest() const { return enable_depth_bounds_test_; }
+
+ void SetDepthBiasConstantFactor(float f) { depth_bias_constant_factor_ = f; }
+ float GetDepthBiasConstantFactor() const {
+ return depth_bias_constant_factor_;
+ }
+
+ void SetDepthBiasClamp(float f) { depth_bias_clamp_ = f; }
+ float GetDepthBiasClamp() const { return depth_bias_clamp_; }
+
+ void SetDepthBiasSlopeFactor(float f) { depth_bias_slope_factor_ = f; }
+ float GetDepthBiasSlopeFactor() const { return depth_bias_slope_factor_; }
+
+ void SetMinDepthBounds(float f) { min_depth_bounds_ = f; }
+ float GetMinDepthBounds() const { return min_depth_bounds_; }
+
+ void SetMaxDepthBounds(float f) { max_depth_bounds_ = f; }
+ float GetMaxDepthBounds() const { return max_depth_bounds_; }
+
+ void SetLogicOp(LogicOp op) { logic_op_ = op; }
+ LogicOp GetLogicOp() const { return logic_op_; }
+
+ void SetSrcColorBlendFactor(BlendFactor f) { src_color_blend_factor_ = f; }
+ BlendFactor GetSrcColorBlendFactor() const { return src_color_blend_factor_; }
+
+ void SetDstColorBlendFactor(BlendFactor f) { dst_color_blend_factor_ = f; }
+ BlendFactor GetDstColorBlendFactor() const { return dst_color_blend_factor_; }
+
+ void SetSrcAlphaBlendFactor(BlendFactor f) { src_alpha_blend_factor_ = f; }
+ BlendFactor GetSrcAlphaBlendFactor() const { return src_alpha_blend_factor_; }
+
+ void SetDstAlphaBlendFactor(BlendFactor f) { dst_alpha_blend_factor_ = f; }
+ BlendFactor GetDstAlphaBlendFactor() const { return dst_alpha_blend_factor_; }
+
+ void SetColorBlendOp(BlendOp op) { color_blend_op_ = op; }
+ BlendOp GetColorBlendOp() const { return color_blend_op_; }
+
+ void SetAlphaBlendOp(BlendOp op) { alpha_blend_op_ = op; }
+ BlendOp GetAlphaBlendOp() const { return alpha_blend_op_; }
+
+ private:
+ StencilOp front_fail_op_ = StencilOp::kKeep;
+ StencilOp front_pass_op_ = StencilOp::kKeep;
+ StencilOp front_depth_fail_op_ = StencilOp::kKeep;
+ CompareOp front_compare_op_ = CompareOp::kAlways;
+
+ StencilOp back_fail_op_ = StencilOp::kKeep;
+ StencilOp back_pass_op_ = StencilOp::kKeep;
+ StencilOp back_depth_fail_op_ = StencilOp::kKeep;
+ CompareOp back_compare_op_ = CompareOp::kAlways;
+
+ Topology topology_ = Topology::kTriangleStrip;
+ PolygonMode polygon_mode_ = PolygonMode::kFill;
+ CullMode cull_mode_ = CullMode::kNone;
+ FrontFace front_face_ = FrontFace::kCounterClockwise;
+ CompareOp depth_compare_op_ = CompareOp::kLess;
+ LogicOp logic_op_ = LogicOp::kClear;
+ BlendFactor src_color_blend_factor_ = BlendFactor::kZero;
+ BlendFactor dst_color_blend_factor_ = BlendFactor::kZero;
+ BlendFactor src_alpha_blend_factor_ = BlendFactor::kZero;
+ BlendFactor dst_alpha_blend_factor_ = BlendFactor::kZero;
+ BlendOp color_blend_op_ = BlendOp::kAdd;
+ BlendOp alpha_blend_op_ = BlendOp::kAdd;
+
+ uint32_t front_compare_mask_ = std::numeric_limits<uint32_t>::max();
+ uint32_t front_write_mask_ = std::numeric_limits<uint32_t>::max();
+ uint32_t front_reference_ = 0;
+
+ uint32_t back_compare_mask_ = std::numeric_limits<uint32_t>::max();
+ uint32_t back_write_mask_ = std::numeric_limits<uint32_t>::max();
+ uint32_t back_reference_ = 0;
+
+ uint8_t color_write_mask_ =
+ kColorMaskR | kColorMaskG | kColorMaskB | kColorMaskA;
+
+ bool enable_blend_ = false;
+ bool enable_depth_test_ = false;
+ bool enable_depth_write_ = false;
+ bool enable_depth_clamp_ = false;
+ bool enable_depth_bias_ = false;
+ bool enable_depth_bounds_test_ = false;
+ bool enable_stencil_test_ = false;
+ bool enable_primitive_restart_ = false;
+ bool enable_rasterizer_discard_ = false;
+ bool enable_logic_op_ = false;
+
+ float line_width_ = 1.0f;
+ float depth_bias_constant_factor_ = 0.0f;
+ float depth_bias_clamp_ = 0.0f;
+ float depth_bias_slope_factor_ = 0.0f;
+ float min_depth_bounds_ = 0.0f;
+ float max_depth_bounds_ = 0.0f;
+};
+
+} // namespace amber
+
+#endif // SRC_PIPELINE_H_
diff --git a/src/result.cc b/src/result.cc
new file mode 100644
index 0000000..39b3a85
--- /dev/null
+++ b/src/result.cc
@@ -0,0 +1,29 @@
+// Copyright 2018 The Amber 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.
+
+#include "amber/result.h"
+
+namespace amber {
+
+Result::Result() : succeeded_(true) {}
+
+Result::Result(const std::string& err) : succeeded_(false), error_(err) {}
+
+Result::Result(const Result&) = default;
+
+Result::~Result() = default;
+
+Result& Result::operator=(const Result&) = default;
+
+} // namespace amber
diff --git a/src/result_test.cc b/src/result_test.cc
new file mode 100644
index 0000000..cb14cb3
--- /dev/null
+++ b/src/result_test.cc
@@ -0,0 +1,41 @@
+// Copyright 2018 The Amber 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.
+
+#include "amber/result.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+
+using ResultTest = testing::Test;
+
+TEST_F(ResultTest, SuccessByDefault) {
+ Result r;
+ EXPECT_TRUE(r.IsSuccess());
+}
+
+TEST_F(ResultTest, ErrorWithString) {
+ Result r("Test Failed");
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Test Failed", r.Error());
+}
+
+TEST_F(ResultTest, Copy) {
+ Result r("Testing");
+ Result r2(r);
+
+ EXPECT_FALSE(r2.IsSuccess());
+ EXPECT_EQ("Testing", r2.Error());
+}
+
+} // namespace amber
diff --git a/src/script.cc b/src/script.cc
new file mode 100644
index 0000000..fa5cbe6
--- /dev/null
+++ b/src/script.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/script.h"
+
+namespace amber {
+
+Script::Script(ScriptType type) : script_type_(type) {}
+
+Script::~Script() = default;
+
+} // namespace amber
diff --git a/src/script.h b/src/script.h
new file mode 100644
index 0000000..4680014
--- /dev/null
+++ b/src/script.h
@@ -0,0 +1,42 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_SCRIPT_H_
+#define SRC_SCRIPT_H_
+
+#include <cstdint>
+
+namespace amber {
+
+enum class ScriptType : uint8_t { kVkScript = 0, kAmberScript };
+
+class Script {
+ public:
+ virtual ~Script();
+
+ bool IsVkScript() const { return script_type_ == ScriptType::kVkScript; }
+ bool IsAmberScript() const {
+ return script_type_ == ScriptType::kAmberScript;
+ }
+
+ protected:
+ Script(ScriptType);
+
+ private:
+ ScriptType script_type_;
+};
+
+} // namespace amber
+
+#endif // SRC_SCRIPT_H_
diff --git a/src/shader_compiler.cc b/src/shader_compiler.cc
new file mode 100644
index 0000000..a5294c7
--- /dev/null
+++ b/src/shader_compiler.cc
@@ -0,0 +1,150 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/shader_compiler.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <iterator>
+
+#include "spirv-tools/libspirv.hpp"
+#include "spirv-tools/linker.hpp"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wold-style-cast"
+#pragma clang diagnostic ignored "-Wshadow-uncaptured-local"
+#pragma clang diagnostic ignored "-Wweak-vtables"
+#include "third_party/shaderc/libshaderc/include/shaderc/shaderc.hpp"
+#pragma clang diagnostic pop
+
+namespace amber {
+
+ShaderCompiler::ShaderCompiler() = default;
+
+ShaderCompiler::~ShaderCompiler() = default;
+
+std::pair<Result, std::vector<uint32_t>> ShaderCompiler::Compile(
+ ShaderType type,
+ ShaderFormat fmt,
+ const std::string& data) const {
+ std::string spv_errors;
+ // TODO(dsinclair): Vulkan env should be an option.
+ spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_0);
+ tools.SetMessageConsumer([&spv_errors](spv_message_level_t level, const char*,
+ const spv_position_t& position,
+ const char* message) {
+ switch (level) {
+ case SPV_MSG_FATAL:
+ case SPV_MSG_INTERNAL_ERROR:
+ case SPV_MSG_ERROR:
+ spv_errors += "error: line " + std::to_string(position.index) + ": " +
+ message + "\n";
+ break;
+ case SPV_MSG_WARNING:
+ spv_errors += "warning: line " + std::to_string(position.index) + ": " +
+ message + "\n";
+ break;
+ case SPV_MSG_INFO:
+ spv_errors += "info: line " + std::to_string(position.index) + ": " +
+ message + "\n";
+ break;
+ case SPV_MSG_DEBUG:
+ break;
+ }
+ });
+
+ std::vector<uint32_t> results;
+ if (fmt == ShaderFormat::kGlsl) {
+ Result r = CompileGlsl(type, data, &results);
+ if (!r.IsSuccess())
+ return {r, {}};
+ } else if (fmt == ShaderFormat::kSpirvAsm) {
+ if (!tools.Assemble(data, &results,
+ spvtools::SpirvTools::kDefaultAssembleOption)) {
+ return {Result("Shader assembly failed: " + spv_errors), {}};
+ }
+ } else if (fmt == ShaderFormat::kSpirvHex) {
+ Result r = ParseHex(data, &results);
+ if (!r.IsSuccess())
+ return {Result("Unable to parse shader hex."), {}};
+ } else {
+ return {Result("Invalid shader format"), results};
+ }
+
+ spvtools::ValidatorOptions options;
+ if (!tools.Validate(results.data(), results.size(), options))
+ return {Result("Invalid shader: " + spv_errors), {}};
+
+ return {{}, results};
+}
+
+Result ShaderCompiler::ParseHex(const std::string& data,
+ std::vector<uint32_t>* result) const {
+ size_t used = 0;
+ const char* str = data.c_str();
+ uint8_t converted = 0;
+ uint32_t tmp = 0;
+ while (used < data.length()) {
+ char* new_pos = nullptr;
+ long v = std::strtol(str, &new_pos, 16);
+
+ ++converted;
+
+ // TODO(dsinclair): Is this actually right?
+ tmp = tmp | (static_cast<uint32_t>(v) << (8 * (converted - 1)));
+ if (converted == 4) {
+ result->push_back(tmp);
+ tmp = 0;
+ converted = 0;
+ }
+
+ used += static_cast<size_t>(new_pos - str);
+ str = new_pos;
+ }
+ return {};
+}
+
+Result ShaderCompiler::CompileGlsl(ShaderType shader_type,
+ const std::string& data,
+ std::vector<uint32_t>* result) const {
+ shaderc::Compiler compiler;
+ shaderc::CompileOptions options;
+
+ shaderc_shader_kind kind;
+ if (shader_type == ShaderType::kCompute)
+ kind = shaderc_compute_shader;
+ else if (shader_type == ShaderType::kFragment)
+ kind = shaderc_fragment_shader;
+ else if (shader_type == ShaderType::kGeometry)
+ kind = shaderc_geometry_shader;
+ else if (shader_type == ShaderType::kVertex)
+ kind = shaderc_vertex_shader;
+ else if (shader_type == ShaderType::kTessellationControl)
+ kind = shaderc_tess_control_shader;
+ else if (shader_type == ShaderType::kTessellationEvaluation)
+ kind = shaderc_tess_evaluation_shader;
+ else
+ return Result("Unknown shader type");
+
+ shaderc::SpvCompilationResult module =
+ compiler.CompileGlslToSpv(data, kind, "-", options);
+
+ if (module.GetCompilationStatus() != shaderc_compilation_status_success)
+ return Result(module.GetErrorMessage());
+
+ std::copy(module.cbegin(), module.cend(), std::back_inserter(*result));
+ return {};
+}
+
+} // namespace amber
diff --git a/src/shader_compiler.h b/src/shader_compiler.h
new file mode 100644
index 0000000..4fd32a6
--- /dev/null
+++ b/src/shader_compiler.h
@@ -0,0 +1,43 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_SHADER_COMPILER_H_
+#define SRC_SHADER_COMPILER_H_
+
+#include <tuple>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/shader_data.h"
+
+namespace amber {
+
+class ShaderCompiler {
+ public:
+ ShaderCompiler();
+ ~ShaderCompiler();
+
+ std::pair<Result, std::vector<uint32_t>>
+ Compile(ShaderType type, ShaderFormat fmt, const std::string& data) const;
+
+ private:
+ Result ParseHex(const std::string& data, std::vector<uint32_t>* result) const;
+ Result CompileGlsl(ShaderType shader_type,
+ const std::string& data,
+ std::vector<uint32_t>* result) const;
+};
+
+} // namespace amber
+
+#endif // SRC_SHADER_COMPILER_H_
diff --git a/src/shader_compiler_test.cc b/src/shader_compiler_test.cc
new file mode 100644
index 0000000..5465331
--- /dev/null
+++ b/src/shader_compiler_test.cc
@@ -0,0 +1,160 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/shader_compiler.h"
+#include "gtest/gtest.h"
+#include "src/vkscript/section_parser.h" // For the passthrough vertex shader
+
+namespace amber {
+namespace {
+
+const char kHexShader[] =
+ R"(0x03 0x02 0x23 0x07 0x00 0x00 0x01 0x00 0x07 0x00 0x08 0x00
+ 0x15 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x11 0x00 0x02 0x00
+ 0x01 0x00 0x00 0x00 0x0b 0x00 0x06 0x00 0x01 0x00 0x00 0x00
+ 0x47 0x4c 0x53 0x4c 0x2e 0x73 0x74 0x64 0x2e 0x34 0x35 0x30
+ 0x00 0x00 0x00 0x00 0x0e 0x00 0x03 0x00 0x00 0x00 0x00 0x00
+ 0x01 0x00 0x00 0x00 0x0f 0x00 0x07 0x00 0x00 0x00 0x00 0x00
+ 0x04 0x00 0x00 0x00 0x6d 0x61 0x69 0x6e 0x00 0x00 0x00 0x00
+ 0x0d 0x00 0x00 0x00 0x11 0x00 0x00 0x00 0x03 0x00 0x03 0x00
+ 0x02 0x00 0x00 0x00 0xae 0x01 0x00 0x00 0x05 0x00 0x04 0x00
+ 0x04 0x00 0x00 0x00 0x6d 0x61 0x69 0x6e 0x00 0x00 0x00 0x00
+ 0x05 0x00 0x06 0x00 0x0b 0x00 0x00 0x00 0x67 0x6c 0x5f 0x50
+ 0x65 0x72 0x56 0x65 0x72 0x74 0x65 0x78 0x00 0x00 0x00 0x00
+ 0x06 0x00 0x06 0x00 0x0b 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x67 0x6c 0x5f 0x50 0x6f 0x73 0x69 0x74 0x69 0x6f 0x6e 0x00
+ 0x06 0x00 0x07 0x00 0x0b 0x00 0x00 0x00 0x01 0x00 0x00 0x00
+ 0x67 0x6c 0x5f 0x50 0x6f 0x69 0x6e 0x74 0x53 0x69 0x7a 0x65
+ 0x00 0x00 0x00 0x00 0x06 0x00 0x07 0x00 0x0b 0x00 0x00 0x00
+ 0x02 0x00 0x00 0x00 0x67 0x6c 0x5f 0x43 0x6c 0x69 0x70 0x44
+ 0x69 0x73 0x74 0x61 0x6e 0x63 0x65 0x00 0x05 0x00 0x03 0x00
+ 0x0d 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x05 0x00 0x05 0x00
+ 0x11 0x00 0x00 0x00 0x70 0x6f 0x73 0x69 0x74 0x69 0x6f 0x6e
+ 0x00 0x00 0x00 0x00 0x48 0x00 0x05 0x00 0x0b 0x00 0x00 0x00
+ 0x00 0x00 0x00 0x00 0x0b 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x48 0x00 0x05 0x00 0x0b 0x00 0x00 0x00 0x01 0x00 0x00 0x00
+ 0x0b 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x48 0x00 0x05 0x00
+ 0x0b 0x00 0x00 0x00 0x02 0x00 0x00 0x00 0x0b 0x00 0x00 0x00
+ 0x03 0x00 0x00 0x00 0x47 0x00 0x03 0x00 0x0b 0x00 0x00 0x00
+ 0x02 0x00 0x00 0x00 0x47 0x00 0x04 0x00 0x11 0x00 0x00 0x00
+ 0x1e 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x13 0x00 0x02 0x00
+ 0x02 0x00 0x00 0x00 0x21 0x00 0x03 0x00 0x03 0x00 0x00 0x00
+ 0x02 0x00 0x00 0x00 0x16 0x00 0x03 0x00 0x06 0x00 0x00 0x00
+ 0x20 0x00 0x00 0x00 0x17 0x00 0x04 0x00 0x07 0x00 0x00 0x00
+ 0x06 0x00 0x00 0x00 0x04 0x00 0x00 0x00 0x15 0x00 0x04 0x00
+ 0x08 0x00 0x00 0x00 0x20 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x2b 0x00 0x04 0x00 0x08 0x00 0x00 0x00 0x09 0x00 0x00 0x00
+ 0x01 0x00 0x00 0x00 0x1c 0x00 0x04 0x00 0x0a 0x00 0x00 0x00
+ 0x06 0x00 0x00 0x00 0x09 0x00 0x00 0x00 0x1e 0x00 0x05 0x00
+ 0x0b 0x00 0x00 0x00 0x07 0x00 0x00 0x00 0x06 0x00 0x00 0x00
+ 0x0a 0x00 0x00 0x00 0x20 0x00 0x04 0x00 0x0c 0x00 0x00 0x00
+ 0x03 0x00 0x00 0x00 0x0b 0x00 0x00 0x00 0x3b 0x00 0x04 0x00
+ 0x0c 0x00 0x00 0x00 0x0d 0x00 0x00 0x00 0x03 0x00 0x00 0x00
+ 0x15 0x00 0x04 0x00 0x0e 0x00 0x00 0x00 0x20 0x00 0x00 0x00
+ 0x01 0x00 0x00 0x00 0x2b 0x00 0x04 0x00 0x0e 0x00 0x00 0x00
+ 0x0f 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x20 0x00 0x04 0x00
+ 0x10 0x00 0x00 0x00 0x01 0x00 0x00 0x00 0x07 0x00 0x00 0x00
+ 0x3b 0x00 0x04 0x00 0x10 0x00 0x00 0x00 0x11 0x00 0x00 0x00
+ 0x01 0x00 0x00 0x00 0x20 0x00 0x04 0x00 0x13 0x00 0x00 0x00
+ 0x03 0x00 0x00 0x00 0x07 0x00 0x00 0x00 0x36 0x00 0x05 0x00
+ 0x02 0x00 0x00 0x00 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
+ 0x03 0x00 0x00 0x00 0xf8 0x00 0x02 0x00 0x05 0x00 0x00 0x00
+ 0x3d 0x00 0x04 0x00 0x07 0x00 0x00 0x00 0x12 0x00 0x00 0x00
+ 0x11 0x00 0x00 0x00 0x41 0x00 0x05 0x00 0x13 0x00 0x00 0x00
+ 0x14 0x00 0x00 0x00 0x0d 0x00 0x00 0x00 0x0f 0x00 0x00 0x00
+ 0x3e 0x00 0x03 0x00 0x14 0x00 0x00 0x00 0x12 0x00 0x00 0x00
+ 0xfd 0x00 0x01 0x00 0x38 0x00 0x01 0x00)";
+
+} // namespace
+
+using ShaderCompilerTest = testing::Test;
+
+TEST_F(ShaderCompilerTest, CompilesGlsl) {
+ std::string contents = R"(
+#version 420
+layout(location = 0) in vec4 position;
+
+void main() {
+ gl_Position = position;
+})";
+
+ ShaderCompiler sc;
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) =
+ sc.Compile(ShaderType::kVertex, ShaderFormat::kGlsl, contents);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(shader.empty());
+ EXPECT_EQ(0x07230203, shader[0]); // Verify SPIR-V header present.
+}
+
+TEST_F(ShaderCompilerTest, CompilesSpirvAsm) {
+ ShaderCompiler sc;
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) = sc.Compile(ShaderType::kVertex, ShaderFormat::kSpirvAsm,
+ kPassThroughShader);
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FALSE(shader.empty());
+ EXPECT_EQ(0x07230203, shader[0]); // Verify SPIR-V header present.
+}
+
+TEST_F(ShaderCompilerTest, CompilesSpirvHex) {
+ ShaderCompiler sc;
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) =
+ sc.Compile(ShaderType::kVertex, ShaderFormat::kSpirvHex, kHexShader);
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FALSE(shader.empty());
+ EXPECT_EQ(0x07230203, shader[0]); // Verify SPIR-V header present.
+}
+
+TEST_F(ShaderCompilerTest, InvalidSpirvHex) {
+ std::string contents = kHexShader;
+ contents[3] = '0';
+
+ ShaderCompiler sc;
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) =
+ sc.Compile(ShaderType::kVertex, ShaderFormat::kSpirvHex, contents);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid shader: error: line 0: Invalid SPIR-V magic number.\n",
+ r.Error());
+}
+
+TEST_F(ShaderCompilerTest, InvalidHex) {
+ ShaderCompiler sc;
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) =
+ sc.Compile(ShaderType::kVertex, ShaderFormat::kSpirvHex, "aaaaaaaaa");
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid shader: error: line 0: Invalid SPIR-V magic number.\n",
+ r.Error());
+}
+
+TEST_F(ShaderCompilerTest, FailsOnInvalidShader) {
+ std::string contents = "Just Random\nText()\nThat doesn't work.";
+
+ ShaderCompiler sc;
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) =
+ sc.Compile(ShaderType::kVertex, ShaderFormat::kGlsl, contents);
+ ASSERT_FALSE(r.IsSuccess());
+}
+
+} // namespace amber
diff --git a/src/shader_data.h b/src/shader_data.h
new file mode 100644
index 0000000..c1fb20e
--- /dev/null
+++ b/src/shader_data.h
@@ -0,0 +1,86 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_SHADER_DATA_H_
+#define SRC_SHADER_DATA_H_
+
+#include <cstdint>
+
+namespace amber {
+
+enum class ShaderFormat : uint8_t {
+ kDefault = 0,
+ kText,
+ kGlsl,
+ kSpirvAsm,
+ kSpirvHex,
+};
+
+enum class ShaderType : uint8_t {
+ kCompute = 0,
+ kGeometry,
+ kFragment,
+ kVertex,
+ kTessellationControl,
+ kTessellationEvaluation,
+};
+
+const char kPassThroughShader[] = R"(; SPIR-V
+; Version: 1.0
+; Generator: Khronos Glslang Reference Front End; 7
+; Bound: 21
+; Schema: 0
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Vertex %main "main" %_ %position
+ OpSource GLSL 430
+ OpName %main "main"
+ OpName %gl_PerVertex "gl_PerVertex"
+ OpMemberName %gl_PerVertex 0 "gl_Position"
+ OpMemberName %gl_PerVertex 1 "gl_PointSize"
+ OpMemberName %gl_PerVertex 2 "gl_ClipDistance"
+ OpName %_ ""
+ OpName %position "position"
+ OpMemberDecorate %gl_PerVertex 0 BuiltIn Position
+ OpMemberDecorate %gl_PerVertex 1 BuiltIn PointSize
+ OpMemberDecorate %gl_PerVertex 2 BuiltIn ClipDistance
+ OpDecorate %gl_PerVertex Block
+ OpDecorate %position Location 0
+ %void = OpTypeVoid
+ %3 = OpTypeFunction %void
+ %float = OpTypeFloat 32
+ %v4float = OpTypeVector %float 4
+ %uint = OpTypeInt 32 0
+ %uint_1 = OpConstant %uint 1
+%_arr_float_uint_1 = OpTypeArray %float %uint_1
+%gl_PerVertex = OpTypeStruct %v4float %float %_arr_float_uint_1
+%_ptr_Output_gl_PerVertex = OpTypePointer Output %gl_PerVertex
+ %_ = OpVariable %_ptr_Output_gl_PerVertex Output
+ %int = OpTypeInt 32 1
+ %int_0 = OpConstant %int 0
+%_ptr_Input_v4float = OpTypePointer Input %v4float
+ %position = OpVariable %_ptr_Input_v4float Input
+%_ptr_Output_v4float = OpTypePointer Output %v4float
+ %main = OpFunction %void None %3
+ %5 = OpLabel
+ %18 = OpLoad %v4float %position
+ %20 = OpAccessChain %_ptr_Output_v4float %_ %int_0
+ OpStore %20 %18
+ OpReturn
+ OpFunctionEnd)";
+
+} // namespace amber
+
+#endif // SRC_SHADER_DATA_H_
diff --git a/src/tokenizer.cc b/src/tokenizer.cc
new file mode 100644
index 0000000..9f55ec8
--- /dev/null
+++ b/src/tokenizer.cc
@@ -0,0 +1,205 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/tokenizer.h"
+
+#include <cstdlib>
+#include <limits>
+#include <sstream>
+
+#include "src/make_unique.h"
+
+namespace amber {
+
+Token::Token(TokenType type) : type_(type) {}
+
+Token::~Token() = default;
+
+Result Token::ConvertToDouble() {
+ if (IsDouble())
+ return {};
+
+ if (IsString() || IsEOL() || IsEOS())
+ return Result("Invalid conversion to double");
+
+ if (IsInteger()) {
+ if (is_negative_ || uint_value_ <= std::numeric_limits<int64_t>::max())
+ double_value_ = static_cast<double>(AsInt64());
+ else
+ return Result("uint64_t value too big to fit in double");
+
+ uint_value_ = 0;
+ } else if (IsHex()) {
+ double_value_ = static_cast<double>(AsHex());
+ string_value_ = "";
+ }
+ type_ = TokenType::kDouble;
+ return {};
+}
+
+Tokenizer::Tokenizer(const std::string& data) : data_(data) {}
+
+Tokenizer::~Tokenizer() = default;
+
+std::unique_ptr<Token> Tokenizer::NextToken() {
+ SkipWhitespace();
+ if (current_position_ >= data_.length())
+ return MakeUnique<Token>(TokenType::kEOS);
+
+ if (data_[current_position_] == '#') {
+ SkipComment();
+ SkipWhitespace();
+ }
+ if (current_position_ >= data_.length())
+ return MakeUnique<Token>(TokenType::kEOS);
+
+ if (data_[current_position_] == '\n') {
+ ++current_line_;
+ ++current_position_;
+ return MakeUnique<Token>(TokenType::kEOL);
+ }
+
+ // If the current position is a , ( or ) then handle it specially as we don't
+ // want to consume any other characters.
+ if (data_[current_position_] == ',' || data_[current_position_] == '(' ||
+ data_[current_position_] == ')') {
+ auto tok = MakeUnique<Token>(TokenType::kString);
+ std::string str(1, data_[current_position_]);
+ tok->SetStringValue(str);
+ ++current_position_;
+ return tok;
+ }
+
+ size_t end_pos = current_position_;
+ while (end_pos < data_.length()) {
+ if (data_[end_pos] == ' ' || data_[end_pos] == '\n' ||
+ data_[end_pos] == ')' || data_[end_pos] == ',' ||
+ data_[end_pos] == '(') {
+ break;
+ }
+ ++end_pos;
+ }
+
+ std::string tok_str =
+ data_.substr(current_position_, end_pos - current_position_);
+ current_position_ = end_pos;
+
+ // Starts with an alpha is a string.
+ if (!std::isdigit(tok_str[0]) &&
+ !(tok_str[0] == '-' && std::isdigit(tok_str[1])) &&
+ !(tok_str[0] == '.' && std::isdigit(tok_str[1]))) {
+ // If we've got a continuation, skip over the end of line and get the next
+ // token.
+ if (tok_str == "\\") {
+ if ((current_position_ < data_.length() &&
+ data_[current_position_] == '\n')) {
+ ++current_line_;
+ ++current_position_;
+ return NextToken();
+ } else if (current_position_ + 1 < data_.length() &&
+ data_[current_position_] == '\r' &&
+ data_[current_position_ + 1] == '\n') {
+ ++current_line_;
+ current_position_ += 2;
+ return NextToken();
+ }
+ }
+
+ auto tok = MakeUnique<Token>(TokenType::kString);
+ tok->SetStringValue(tok_str);
+ return tok;
+ }
+
+ // Handle hex strings
+ if (tok_str.size() > 2 && tok_str[0] == '0' && tok_str[1] == 'x') {
+ auto tok = MakeUnique<Token>(TokenType::kHex);
+ tok->SetStringValue(tok_str);
+ return tok;
+ }
+
+ bool is_double = false;
+ for (const char ch : tok_str) {
+ if (ch == '.') {
+ is_double = true;
+ break;
+ }
+ }
+
+ std::unique_ptr<Token> tok;
+
+ char* final_pos = nullptr;
+ if (is_double) {
+ tok = MakeUnique<Token>(TokenType::kDouble);
+
+ double val = strtod(tok_str.c_str(), &final_pos);
+ tok->SetDoubleValue(val);
+ } else {
+ tok = MakeUnique<Token>(TokenType::kInteger);
+
+ uint64_t val = strtoull(tok_str.c_str(), &final_pos, 10);
+ tok->SetUint64Value(static_cast<uint64_t>(val));
+ }
+ if (tok_str.size() > 1 && tok_str[0] == '-')
+ tok->SetNegative();
+
+ // If the number isn't the whole token then move back so we can then parse
+ // the string portion.
+ long diff = final_pos - tok_str.c_str();
+ if (diff > 0)
+ current_position_ -= (tok_str.length() - static_cast<size_t>(diff));
+
+ return tok;
+}
+
+std::string Tokenizer::ExtractToNext(const std::string& str) {
+ size_t pos = data_.find(str, current_position_);
+ std::string ret;
+ if (pos == std::string::npos) {
+ ret = data_.substr(current_position_);
+ current_position_ = data_.length();
+ } else {
+ ret = data_.substr(current_position_, pos - current_position_);
+ current_position_ = pos;
+ }
+
+ // Account for any new lines in the extracted text so our current line
+ // number stays correct.
+ for (const char c : ret) {
+ if (c == '\n')
+ ++current_line_;
+ }
+
+ return ret;
+}
+
+bool Tokenizer::IsWhitespace(char ch) {
+ return ch == '\0' || ch == '\t' || ch == '\r' || ch == 0x0c /* ff */ ||
+ ch == ' ';
+}
+
+void Tokenizer::SkipWhitespace() {
+ while (current_position_ < data_.size() &&
+ IsWhitespace(data_[current_position_])) {
+ ++current_position_;
+ }
+}
+
+void Tokenizer::SkipComment() {
+ while (current_position_ < data_.length() &&
+ data_[current_position_] != '\n') {
+ ++current_position_;
+ }
+}
+
+} // namespace amber
diff --git a/src/tokenizer.h b/src/tokenizer.h
new file mode 100644
index 0000000..435f1db
--- /dev/null
+++ b/src/tokenizer.h
@@ -0,0 +1,111 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_TOKENIZER_H_
+#define SRC_TOKENIZER_H_
+
+#include <memory>
+#include <string>
+
+#include "amber/result.h"
+
+namespace amber {
+
+enum class TokenType : uint8_t {
+ kEOS = 0,
+ kEOL,
+ kString,
+ kInteger,
+ kDouble,
+ kHex,
+};
+
+class Token {
+ public:
+ Token(TokenType type);
+ ~Token();
+
+ bool IsHex() const { return type_ == TokenType::kHex; }
+ bool IsInteger() const { return type_ == TokenType::kInteger; }
+ bool IsDouble() const { return type_ == TokenType::kDouble; }
+ bool IsString() const { return type_ == TokenType::kString; }
+ bool IsEOS() const { return type_ == TokenType::kEOS; }
+ bool IsEOL() const { return type_ == TokenType::kEOL; }
+
+ bool IsComma() const {
+ return type_ == TokenType::kString && string_value_ == ",";
+ }
+ bool IsOpenBracket() const {
+ return type_ == TokenType::kString && string_value_ == "(";
+ }
+ bool IsCloseBracket() const {
+ return type_ == TokenType::kString && string_value_ == ")";
+ }
+
+ void SetNegative() { is_negative_ = true; }
+ void SetStringValue(const std::string& val) { string_value_ = val; }
+ void SetUint64Value(uint64_t val) { uint_value_ = val; }
+ void SetDoubleValue(double val) { double_value_ = val; }
+
+ const std::string& AsString() const { return string_value_; }
+
+ uint8_t AsUint8() const { return static_cast<uint8_t>(uint_value_); }
+ uint16_t AsUint16() const { return static_cast<uint16_t>(uint_value_); }
+ uint32_t AsUint32() const { return static_cast<uint32_t>(uint_value_); }
+ uint64_t AsUint64() const { return static_cast<uint64_t>(uint_value_); }
+
+ int8_t AsInt8() const { return static_cast<int8_t>(uint_value_); }
+ int16_t AsInt16() const { return static_cast<int16_t>(uint_value_); }
+ int32_t AsInt32() const { return static_cast<int32_t>(uint_value_); }
+ int64_t AsInt64() const { return static_cast<int64_t>(uint_value_); }
+
+ Result ConvertToDouble();
+
+ float AsFloat() const { return static_cast<float>(double_value_); }
+ double AsDouble() const { return double_value_; }
+
+ uint64_t AsHex() const {
+ return strtoull(string_value_.c_str(), nullptr, 16);
+ }
+
+ private:
+ TokenType type_;
+ std::string string_value_;
+ uint64_t uint_value_ = 0;
+ double double_value_ = 0.0;
+ bool is_negative_ = false;
+};
+
+class Tokenizer {
+ public:
+ Tokenizer(const std::string& data);
+ ~Tokenizer();
+
+ std::unique_ptr<Token> NextToken();
+ std::string ExtractToNext(const std::string& str);
+ size_t GetCurrentLine() const { return current_line_; }
+
+ private:
+ bool IsWhitespace(char ch);
+ void SkipWhitespace();
+ void SkipComment();
+
+ std::string data_;
+ size_t current_position_ = 0;
+ size_t current_line_ = 1;
+};
+
+} // namespace amber
+
+#endif // SRC_TOKENIZER_H_
diff --git a/src/tokenizer_test.cc b/src/tokenizer_test.cc
new file mode 100644
index 0000000..fa74da7
--- /dev/null
+++ b/src/tokenizer_test.cc
@@ -0,0 +1,554 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/tokenizer.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+
+using TokenizerTest = testing::Test;
+
+TEST_F(TokenizerTest, ProcessEmpty) {
+ Tokenizer t("");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessString) {
+ Tokenizer t("TestString");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("TestString", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessInt) {
+ Tokenizer t("123");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsInteger());
+ EXPECT_EQ(123U, next->AsUint32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessNegative) {
+ Tokenizer t("-123");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsInteger());
+ EXPECT_EQ(-123, next->AsInt32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessDouble) {
+ Tokenizer t("123.456");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_EQ(123.456f, next->AsFloat());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessNegativeDouble) {
+ Tokenizer t("-123.456");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_EQ(-123.456f, next->AsFloat());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessDoubleStartWithDot) {
+ Tokenizer t(".123456");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_EQ(.123456f, next->AsFloat());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessStringWithNumberInName) {
+ Tokenizer t("BufferAccess32");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("BufferAccess32", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessMultiStatement) {
+ Tokenizer t("TestValue 123.456");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("TestValue", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_EQ(123.456f, next->AsFloat());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessMultiLineStatement) {
+ Tokenizer t("TestValue 123.456\nAnotherValue\n\nThirdValue 456");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("TestValue", next->AsString());
+ EXPECT_EQ(1U, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_EQ(123.456f, next->AsFloat());
+ EXPECT_EQ(1U, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("AnotherValue", next->AsString());
+ EXPECT_EQ(2U, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("ThirdValue", next->AsString());
+ EXPECT_EQ(4U, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsInteger());
+ EXPECT_EQ(456U, next->AsUint16());
+ EXPECT_EQ(4U, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ProcessComments) {
+ Tokenizer t(R"(# Initial comment string
+TestValue 123.456
+ AnotherValue # Space before, comment after
+
+ThirdValue 456)");
+ auto next = t.NextToken();
+ // The comment injects a blank line into the output
+ // so we can handle full line comment and end of line comment the same.
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("TestValue", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_EQ(123.456f, next->AsFloat());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("AnotherValue", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOL());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("ThirdValue", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsInteger());
+ EXPECT_EQ(456U, next->AsUint16());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, HexValue) {
+ Tokenizer t("0xff00f0ff");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsHex());
+ EXPECT_EQ(0xff00f0ff, next->AsHex());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, HexValueAfterWhiteSpace) {
+ Tokenizer t(" \t \t 0xff00f0ff");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsHex());
+ EXPECT_EQ(0xff00f0ff, next->AsHex());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, StringStartingWithNum) {
+ Tokenizer t("1/ABC");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsInteger());
+ EXPECT_EQ(1U, next->AsUint32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("/ABC", next->AsString());
+}
+
+TEST_F(TokenizerTest, BracketsAndCommas) {
+ Tokenizer t("(1.0, 2, abc)");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsOpenBracket());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsDouble());
+ EXPECT_FLOAT_EQ(1.0, next->AsFloat());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsComma());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsInteger());
+ EXPECT_EQ(2U, next->AsUint32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsComma());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("abc", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsCloseBracket());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromDouble) {
+ Tokenizer t("-1.234");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsDouble());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FLOAT_EQ(-1.234, next->AsFloat());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromInt) {
+ Tokenizer t("-1");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FLOAT_EQ(-1.0, next->AsFloat());
+}
+
+TEST_F(TokenizerTest, DashToken) {
+ Tokenizer t("-");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsString());
+ EXPECT_EQ("-", next->AsString());
+}
+
+TEST_F(TokenizerTest, ParseUint64Max) {
+ Tokenizer t(std::to_string(std::numeric_limits<uint64_t>::max()));
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(std::numeric_limits<uint64_t>::max(), next->AsUint64());
+}
+
+TEST_F(TokenizerTest, ParseInt64Min) {
+ Tokenizer t(std::to_string(std::numeric_limits<int64_t>::min()));
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(std::numeric_limits<int64_t>::min(), next->AsInt64());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromUint64Max) {
+ Tokenizer t(std::to_string(std::numeric_limits<uint64_t>::max()));
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("uint64_t value too big to fit in double", r.Error());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromInt64Min) {
+ Tokenizer t(std::to_string(std::numeric_limits<int64_t>::min()));
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FLOAT_EQ(std::numeric_limits<int64_t>::min(), next->AsDouble());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromInt64Max) {
+ Tokenizer t(std::to_string(std::numeric_limits<int64_t>::max()));
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FLOAT_EQ(std::numeric_limits<int64_t>::max(), next->AsDouble());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromString) {
+ Tokenizer t("INVALID");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsString());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromHex) {
+ Tokenizer t("0xff00f0ff");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsHex());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_FLOAT_EQ(0xff00f0ff, next->AsFloat());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromEOS) {
+ Tokenizer t("");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsEOS());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(TokenizerTest, TokenToDoubleFromEOL) {
+ Tokenizer t("-1\n-2");
+ auto next = t.NextToken();
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsEOL());
+
+ Result r = next->ConvertToDouble();
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(TokenizerTest, Continuations) {
+ Tokenizer t("1 \\\n2");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(1, next->AsInt32());
+ EXPECT_EQ(1, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(2, next->AsInt32());
+ EXPECT_EQ(2, t.GetCurrentLine());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ContinuationAtEndOfString) {
+ Tokenizer t("1 \\");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(1, next->AsInt32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsString());
+ EXPECT_EQ("\\", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ContinuationTokenAtOfLine) {
+ Tokenizer t("1 \\2");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(1, next->AsInt32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsString());
+ EXPECT_EQ("\\2", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ContinuationTokenInMiddleOfLine) {
+ Tokenizer t("1 \\ 2");
+ auto next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(1, next->AsInt32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsString());
+ EXPECT_EQ("\\", next->AsString());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ ASSERT_TRUE(next->IsInteger());
+ EXPECT_EQ(2U, next->AsInt32());
+
+ next = t.NextToken();
+ ASSERT_TRUE(next != nullptr);
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ExtractToNext) {
+ Tokenizer t("this\nis\na\ntest\nEND");
+
+ auto next = t.NextToken();
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("this", next->AsString());
+
+ std::string s = t.ExtractToNext("END");
+ ASSERT_EQ("\nis\na\ntest\n", s);
+
+ next = t.NextToken();
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("END", next->AsString());
+ EXPECT_EQ(5U, t.GetCurrentLine());
+
+ next = t.NextToken();
+ EXPECT_TRUE(next->IsEOS());
+}
+
+TEST_F(TokenizerTest, ExtractToNextMissingNext) {
+ Tokenizer t("this\nis\na\ntest\n");
+
+ auto next = t.NextToken();
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("this", next->AsString());
+
+ std::string s = t.ExtractToNext("END");
+ ASSERT_EQ("\nis\na\ntest\n", s);
+
+ next = t.NextToken();
+ EXPECT_TRUE(next->IsEOS());
+ EXPECT_EQ(5U, t.GetCurrentLine());
+}
+
+TEST_F(TokenizerTest, ExtractToNextCurrentIsNext) {
+ Tokenizer t("END");
+ std::string s = t.ExtractToNext("END");
+ ASSERT_EQ("", s);
+
+ auto next = t.NextToken();
+ EXPECT_TRUE(next->IsString());
+ EXPECT_EQ("END", next->AsString());
+
+ next = t.NextToken();
+ EXPECT_TRUE(next->IsEOS());
+}
+
+} // namespace amber
diff --git a/src/value.cc b/src/value.cc
new file mode 100644
index 0000000..3bc5e5f
--- /dev/null
+++ b/src/value.cc
@@ -0,0 +1,25 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/value.h"
+
+namespace amber {
+
+Value::Value() = default;
+
+Value::Value(const Value&) = default;
+
+Value::~Value() = default;
+
+} // namespace amber
diff --git a/src/value.h b/src/value.h
new file mode 100644
index 0000000..06de7d6
--- /dev/null
+++ b/src/value.h
@@ -0,0 +1,62 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VALUE_H_
+#define SRC_VALUE_H_
+
+#include <cstdint>
+
+namespace amber {
+
+class Value {
+ public:
+ Value();
+ Value(const Value&);
+ ~Value();
+
+ void SetIntValue(uint64_t val) {
+ type_ = Type::kInteger;
+ uint_value_ = val;
+ }
+ bool IsInteger() const { return type_ == Type::kInteger; }
+
+ void SetDoubleValue(double val) {
+ type_ = Type::kFloat;
+ double_value_ = val;
+ }
+ bool IsFloat() const { return type_ == Type::kFloat; }
+
+ uint8_t AsUint8() const { return static_cast<uint8_t>(uint_value_); }
+ uint16_t AsUint16() const { return static_cast<uint16_t>(uint_value_); }
+ uint32_t AsUint32() const { return static_cast<uint32_t>(uint_value_); }
+ uint64_t AsUint64() const { return static_cast<uint64_t>(uint_value_); }
+
+ int8_t AsInt8() const { return static_cast<int8_t>(uint_value_); }
+ int16_t AsInt16() const { return static_cast<int16_t>(uint_value_); }
+ int32_t AsInt32() const { return static_cast<int32_t>(uint_value_); }
+ int64_t AsInt64() const { return static_cast<int64_t>(uint_value_); }
+
+ float AsFloat() const { return static_cast<float>(double_value_); }
+ double AsDouble() const { return double_value_; }
+
+ private:
+ enum class Type { kFloat, kInteger };
+ Type type_ = Type::kFloat;
+ uint64_t uint_value_ = 0;
+ double double_value_ = 0.0;
+};
+
+} // namespace amber
+
+#endif // SRC_VALUE_H_
diff --git a/src/vkscript/command_parser.cc b/src/vkscript/command_parser.cc
new file mode 100644
index 0000000..af10525
--- /dev/null
+++ b/src/vkscript/command_parser.cc
@@ -0,0 +1,1856 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/command_parser.h"
+
+#include <algorithm>
+#include <cassert>
+
+#include "src/command_data.h"
+#include "src/make_unique.h"
+#include "src/tokenizer.h"
+#include "src/vkscript/datum_type_parser.h"
+
+namespace amber {
+namespace vkscript {
+namespace {
+
+ShaderType ShaderNameToType(const std::string& name) {
+ if (name == "fragment")
+ return ShaderType::kFragment;
+ if (name == "compute")
+ return ShaderType::kCompute;
+ if (name == "geometry")
+ return ShaderType::kGeometry;
+ if (name == "tessellation evaluation")
+ return ShaderType::kTessellationEvaluation;
+ if (name == "tessellation control")
+ return ShaderType::kTessellationControl;
+
+ return ShaderType::kVertex;
+}
+
+} // namespace
+
+CommandParser::CommandParser() = default;
+
+CommandParser::~CommandParser() = default;
+
+Result CommandParser::ParseBoolean(const std::string& str, bool* result) {
+ assert(result);
+
+ std::string tmp;
+ tmp.resize(str.size());
+ std::transform(str.begin(), str.end(), tmp.begin(),
+ [](unsigned char c) { return std::tolower(c); });
+
+ if (tmp == "true") {
+ *result = true;
+ return {};
+ }
+ if (tmp == "false") {
+ *result = false;
+ return {};
+ }
+ return Result("Invalid value passed as a boolean string");
+}
+
+Result CommandParser::Parse(const std::string& data) {
+ tokenizer_ = MakeUnique<Tokenizer>(data);
+
+ for (auto token = tokenizer_->NextToken(); !token->IsEOS();
+ token = tokenizer_->NextToken()) {
+ if (token->IsEOL())
+ continue;
+
+ if (!token->IsString()) {
+ return Result(
+ "Command not recognized. Received something other then a string.");
+ }
+
+ std::string cmd_name = token->AsString();
+ Result r;
+ if (cmd_name == "draw") {
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid draw command in test");
+
+ cmd_name = token->AsString();
+ if (cmd_name == "rect")
+ r = ProcessDrawRect();
+ else if (cmd_name == "arrays")
+ r = ProcessDrawArrays();
+ else
+ return Result("Unknown draw command: " + cmd_name);
+
+ } else if (cmd_name == "clear") {
+ r = ProcessClear();
+ } else if (cmd_name == "ssbo") {
+ r = ProcessSSBO();
+ } else if (cmd_name == "uniform") {
+ r = ProcessUniform();
+ } else if (cmd_name == "patch") {
+ r = ProcessPatch();
+ } else if (cmd_name == "probe") {
+ r = ProcessProbe(false);
+ } else if (cmd_name == "tolerance") {
+ r = ProcessTolerance();
+ } else if (cmd_name == "relative") {
+ token = tokenizer_->NextToken();
+ if (!token->IsString() || token->AsString() != "probe")
+ return Result("relative must be used with probe");
+
+ r = ProcessProbe(true);
+ } else if (cmd_name == "compute") {
+ r = ProcessCompute();
+ } else if (cmd_name == "vertex" || cmd_name == "fragment" ||
+ cmd_name == "geometry" || cmd_name == "tessellation") {
+ std::string shader_name = cmd_name;
+ if (cmd_name == "tessellation") {
+ token = tokenizer_->NextToken();
+ if (!token->IsString() || (token->AsString() != "control" &&
+ token->AsString() != "evaluation")) {
+ return Result(
+ "Tessellation entrypoint must have <evaluation|control> in name");
+ }
+ shader_name += " " + token->AsString();
+ }
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString() || token->AsString() != "entrypoint")
+ return Result("Unknown command: " + shader_name);
+
+ r = ProcessEntryPoint(shader_name);
+
+ // Pipeline Commands
+ } else if (cmd_name == "primitiveRestartEnable") {
+ r = ProcessPrimitiveRestartEnable();
+ } else if (cmd_name == "depthClampEnable") {
+ r = ProcessDepthClampEnable();
+ } else if (cmd_name == "rasterizerDiscardEnable") {
+ r = ProcessRasterizerDiscardEnable();
+ } else if (cmd_name == "depthBiasEnable") {
+ r = ProcessDepthBiasEnable();
+ } else if (cmd_name == "logicOpEnable") {
+ r = ProcessLogicOpEnable();
+ } else if (cmd_name == "blendEnable") {
+ r = ProcessBlendEnable();
+ } else if (cmd_name == "depthTestEnable") {
+ r = ProcessDepthTestEnable();
+ } else if (cmd_name == "depthWriteEnable") {
+ r = ProcessDepthWriteEnable();
+ } else if (cmd_name == "depthBoundsTestEnable") {
+ r = ProcessDepthBoundsTestEnable();
+ } else if (cmd_name == "stencilTestEnable") {
+ r = ProcessStencilTestEnable();
+ } else if (cmd_name == "topology") {
+ r = ProcessTopology();
+ } else if (cmd_name == "polygonMode") {
+ r = ProcessPolygonMode();
+ } else if (cmd_name == "logicOp") {
+ r = ProcessLogicOp();
+ } else if (cmd_name == "frontFace") {
+ r = ProcessFrontFace();
+ } else if (cmd_name == "cullMode") {
+ r = ProcessCullMode();
+ } else if (cmd_name == "depthBiasConstantFactor") {
+ r = ProcessDepthBiasConstantFactor();
+ } else if (cmd_name == "depthBiasClamp") {
+ r = ProcessDepthBiasClamp();
+ } else if (cmd_name == "depthBiasSlopeFactor") {
+ r = ProcessDepthBiasSlopeFactor();
+ } else if (cmd_name == "lineWidth") {
+ r = ProcessLineWidth();
+ } else if (cmd_name == "minDepthBounds") {
+ r = ProcessMinDepthBounds();
+ } else if (cmd_name == "maxDepthBounds") {
+ r = ProcessMaxDepthBounds();
+ } else if (cmd_name == "srcColorBlendFactor") {
+ r = ProcessSrcColorBlendFactor();
+ } else if (cmd_name == "dstColorBlendFactor") {
+ r = ProcessDstColorBlendFactor();
+ } else if (cmd_name == "srcAlphaBlendFactor") {
+ r = ProcessSrcAlphaBlendFactor();
+ } else if (cmd_name == "dstAlphaBlendFactor") {
+ r = ProcessDstAlphaBlendFactor();
+ } else if (cmd_name == "colorBlendOp") {
+ r = ProcessColorBlendOp();
+ } else if (cmd_name == "alphaBlendOp") {
+ r = ProcessAlphaBlendOp();
+ } else if (cmd_name == "depthCompareOp") {
+ r = ProcessDepthCompareOp();
+ } else if (cmd_name == "front.compareOp") {
+ r = ProcessFrontCompareOp();
+ } else if (cmd_name == "back.compareOp") {
+ r = ProcessBackCompareOp();
+ } else if (cmd_name == "front.failOp") {
+ r = ProcessFrontFailOp();
+ } else if (cmd_name == "front.passOp") {
+ r = ProcessFrontPassOp();
+ } else if (cmd_name == "front.depthFailOp") {
+ r = ProcessFrontDepthFailOp();
+ } else if (cmd_name == "back.failOp") {
+ r = ProcessBackFailOp();
+ } else if (cmd_name == "back.passOp") {
+ r = ProcessBackPassOp();
+ } else if (cmd_name == "back.depthFailOp") {
+ r = ProcessBackDepthFailOp();
+ } else if (cmd_name == "front.compareMask") {
+ r = ProcessFrontCompareMask();
+ } else if (cmd_name == "front.writeMask") {
+ r = ProcessFrontWriteMask();
+ } else if (cmd_name == "back.compareMask") {
+ r = ProcessBackCompareMask();
+ } else if (cmd_name == "back.writeMask") {
+ r = ProcessBackWriteMask();
+ } else if (cmd_name == "front.reference") {
+ r = ProcessFrontReference();
+ } else if (cmd_name == "back.reference") {
+ r = ProcessBackReference();
+ } else if (cmd_name == "colorWriteMask") {
+ r = ProcessColorWriteMask();
+ } else {
+ return Result("Unknown command: " + cmd_name);
+ }
+
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ return {};
+}
+
+Result CommandParser::ProcessDrawRect() {
+ auto cmd = MakeUnique<DrawRectCommand>(pipeline_data_);
+
+ auto token = tokenizer_->NextToken();
+ while (token->IsString()) {
+ std::string str = token->AsString();
+ if (str != "ortho" && str != "patch")
+ return Result("Unknown parameter to draw rect: " + str);
+
+ if (str == "ortho") {
+ cmd->EnableOrtho();
+ } else {
+ cmd->EnablePatch();
+ }
+ token = tokenizer_->NextToken();
+ }
+
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetX(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetY(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetWidth(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetHeight(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter to draw rect command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessDrawArrays() {
+ auto cmd = MakeUnique<DrawArraysCommand>(pipeline_data_);
+
+ auto token = tokenizer_->NextToken();
+ while (token->IsString()) {
+ std::string str = token->AsString();
+ if (str != "indexed" && str != "instanced") {
+ Topology topo = NameToTopology(token->AsString());
+ if (topo != Topology::kUnknown) {
+ cmd->SetTopology(topo);
+
+ // Advance token here so we're consistent with the non-topology case.
+ token = tokenizer_->NextToken();
+ break;
+ }
+ return Result("Unknown parameter to draw arrays: " + str);
+ }
+
+ if (str == "indexed") {
+ cmd->EnableIndexed();
+ } else {
+ cmd->EnableInstanced();
+ }
+ token = tokenizer_->NextToken();
+ }
+
+ if (cmd->GetTopology() == Topology::kUnknown)
+ return Result("Missing draw arrays topology");
+
+ if (!token->IsInteger())
+ return Result("Missing integer first vertex value for draw arrays");
+ cmd->SetFirstVertexIndex(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Missing integer vertex count value for draw arrays");
+ cmd->SetVertexCount(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (cmd->IsInstanced()) {
+ if (!token->IsEOL() && !token->IsEOS()) {
+ if (!token->IsInteger())
+ return Result("Invalid instance count for draw arrays");
+
+ cmd->SetInstanceCount(token->AsUint32());
+ }
+ token = tokenizer_->NextToken();
+ }
+
+ if (!token->IsEOL() && !token->IsEOS())
+ return Result("Extra parameter to draw arrays command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessCompute() {
+ auto cmd = MakeUnique<ComputeCommand>(pipeline_data_);
+
+ auto token = tokenizer_->NextToken();
+
+ // Compute can start a compute line or an entryp oint line ...
+ if (token->IsString() && token->AsString() == "entrypoint")
+ return ProcessEntryPoint("compute");
+
+ if (!token->IsInteger())
+ return Result("Missing integer value for compute X entry");
+ cmd->SetX(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Missing integer value for compute Y entry");
+ cmd->SetY(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Missing integer value for compute Z entry");
+ cmd->SetZ(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter to compute command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessClear() {
+ std::unique_ptr<Command> cmd;
+
+ auto token = tokenizer_->NextToken();
+ std::string cmd_suffix = "";
+ if (token->IsString()) {
+ std::string str = token->AsString();
+ cmd_suffix = str + " ";
+ if (str == "depth") {
+ cmd = MakeUnique<ClearDepthCommand>();
+
+ token = tokenizer_->NextToken();
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->AsClearDepth()->SetValue(token->AsFloat());
+ } else if (str == "stencil") {
+ cmd = MakeUnique<ClearStencilCommand>();
+
+ token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing stencil value for clear stencil command");
+ if (!token->IsInteger())
+ return Result("Invalid stencil value for clear stencil command");
+
+ cmd->AsClearStencil()->SetValue(token->AsUint32());
+ } else if (str == "color") {
+ cmd = MakeUnique<ClearColorCommand>();
+
+ token = tokenizer_->NextToken();
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->AsClearColor()->SetR(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->AsClearColor()->SetG(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->AsClearColor()->SetB(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->AsClearColor()->SetA(token->AsFloat());
+ } else {
+ return Result("Extra parameter to clear command");
+ }
+
+ token = tokenizer_->NextToken();
+ } else {
+ cmd = MakeUnique<ClearCommand>();
+ }
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter to clear " + cmd_suffix + "command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ParseValues(const std::string& name,
+ const DatumType& type,
+ std::vector<Value>* values) {
+ assert(values);
+
+ auto token = tokenizer_->NextToken();
+ while (!token->IsEOL() && !token->IsEOS()) {
+ Value v;
+
+ if ((type.IsFloat() || type.IsDouble())) {
+ if (!token->IsInteger() && !token->IsDouble()) {
+ return Result(std::string("Invalid value provided to ") + name +
+ " command");
+ }
+
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+
+ v.SetDoubleValue(token->AsDouble());
+ } else {
+ if (!token->IsInteger()) {
+ return Result(std::string("Invalid value provided to ") + name +
+ " command");
+ }
+
+ v.SetIntValue(token->AsUint64());
+ }
+
+ values->push_back(v);
+ token = tokenizer_->NextToken();
+ }
+
+ size_t seen = values->size();
+ // This could overflow, but I don't really expect us to get command files
+ // that big ....
+ size_t num_per_row = type.ColumnCount() * type.RowCount();
+ if (seen == 0 || (seen % num_per_row) != 0) {
+ return Result(std::string("Incorrect number of values provided to ") +
+ name + " command");
+ }
+
+ return {};
+}
+
+Result CommandParser::ProcessSSBO() {
+ auto cmd = MakeUnique<BufferCommand>(BufferCommand::BufferType::kSSBO);
+
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing binding and size values for ssbo command");
+ if (!token->IsInteger())
+ return Result("Invalid binding value for ssbo command");
+
+ uint32_t val = token->AsUint32();
+
+ token = tokenizer_->NextToken();
+ if (token->IsString() && token->AsString() != "subdata") {
+ auto& str = token->AsString();
+ if (str.size() >= 2 && str[0] == ':') {
+ cmd->SetDescriptorSet(val);
+
+ auto substr = str.substr(1, str.size());
+ uint64_t binding_val = strtoul(substr.c_str(), nullptr, 10);
+ if (binding_val > std::numeric_limits<uint32_t>::max())
+ return Result("binding value too large in ssbo command");
+
+ cmd->SetBinding(static_cast<uint32_t>(binding_val));
+ } else {
+ return Result("Invalid value for ssbo command");
+ }
+
+ token = tokenizer_->NextToken();
+ } else {
+ cmd->SetBinding(val);
+ }
+
+ if (token->IsString() && token->AsString() == "subdata") {
+ cmd->SetIsSubdata();
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid type for ssbo command");
+
+ DatumTypeParser tp;
+ Result r = tp.Parse(token->AsString());
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetDatumType(tp.GetType());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Invalid offset for ssbo command");
+
+ cmd->SetOffset(token->AsUint32());
+
+ std::vector<Value> values;
+ r = ParseValues("ssbo", cmd->GetDatumType(), &values);
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetValues(std::move(values));
+
+ } else {
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing size value for ssbo command");
+ if (!token->IsInteger())
+ return Result("Invalid size value for ssbo command");
+
+ cmd->SetSize(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for ssbo command");
+ }
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessUniform() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing binding and size values for uniform command");
+ if (!token->IsString())
+ return Result("Invalid type value for uniform command");
+
+ std::unique_ptr<BufferCommand> cmd;
+ if (token->AsString() == "ubo") {
+ cmd = MakeUnique<BufferCommand>(BufferCommand::BufferType::kUniform);
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Invalid binding value for uniform ubo command");
+
+ uint32_t val = token->AsUint32();
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid type value for uniform ubo command");
+
+ auto& str = token->AsString();
+ if (str.size() >= 2 && str[0] == ':') {
+ cmd->SetDescriptorSet(val);
+
+ auto substr = str.substr(1, str.size());
+ uint64_t binding_val = strtoul(substr.c_str(), nullptr, 10);
+ if (binding_val > std::numeric_limits<uint32_t>::max())
+ return Result("binding value too large in uniform ubo command");
+
+ cmd->SetBinding(static_cast<uint32_t>(binding_val));
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid type value for uniform ubo command");
+ } else {
+ cmd->SetBinding(val);
+ }
+
+ } else {
+ cmd = MakeUnique<BufferCommand>(BufferCommand::BufferType::kPushConstant);
+ }
+
+ DatumTypeParser tp;
+ Result r = tp.Parse(token->AsString());
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetDatumType(tp.GetType());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Invalid offset value for uniform command");
+
+ cmd->SetOffset(token->AsUint32());
+
+ std::vector<Value> values;
+ r = ParseValues("uniform", cmd->GetDatumType(), &values);
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetValues(std::move(values));
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessTolerance() {
+ auto cmd = MakeUnique<ToleranceCommand>();
+
+ auto token = tokenizer_->NextToken();
+ size_t found_tokens = 0;
+ while (!token->IsEOL() && !token->IsEOS() && found_tokens < 4) {
+ if (token->IsString() && token->AsString() == ",") {
+ token = tokenizer_->NextToken();
+ continue;
+ }
+
+ if (token->IsInteger() || token->IsDouble()) {
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ double value = token->AsDouble();
+
+ token = tokenizer_->NextToken();
+ if (token->IsString() && token->AsString() != ",") {
+ if (token->AsString() != "%")
+ return Result("Invalid value for tolerance command");
+
+ cmd->AddPercentTolerance(value);
+ token = tokenizer_->NextToken();
+ } else {
+ cmd->AddValueTolerance(value);
+ }
+ } else {
+ return Result("Invalid value for tolerance command");
+ }
+
+ ++found_tokens;
+ }
+ if (found_tokens == 0)
+ return Result("Missing value for tolerance command");
+ if (found_tokens != 1 && found_tokens != 4)
+ return Result("Invalid number of tolerance parameters provided");
+
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for tolerance command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessPatch() {
+ auto cmd = MakeUnique<PatchParameterVerticesCommand>();
+
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString() || token->AsString() != "parameter")
+ return Result("Missing parameter flag to patch command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString() || token->AsString() != "vertices")
+ return Result("Missing vertices flag to patch command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Invalid count parameter for patch parameter vertices");
+ cmd->SetControlPointCount(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for patch parameter vertices command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessEntryPoint(const std::string& name) {
+ auto cmd = MakeUnique<EntryPointCommand>();
+
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing entrypoint name");
+
+ if (!token->IsString())
+ return Result("Entrypoint name must be a string");
+
+ cmd->SetShaderType(ShaderNameToType(name));
+ cmd->SetEntryPointName(token->AsString());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for entrypoint command");
+
+ commands_.push_back(std::move(cmd));
+
+ return {};
+}
+
+Result CommandParser::ProcessProbe(bool relative) {
+ auto token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid token in probe command");
+
+ // The SSBO syntax is different from probe or probe all so handle specially.
+ if (token->AsString() == "ssbo")
+ return ProcessProbeSSBO();
+
+ auto cmd = MakeUnique<ProbeCommand>();
+ if (relative)
+ cmd->SetRelative();
+
+ bool is_rect = false;
+ if (token->AsString() == "rect") {
+ is_rect = true;
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid token in probe command");
+ } else if (token->AsString() == "all") {
+ cmd->SetWholeWindow();
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid token in probe command");
+ }
+
+ std::string format = token->AsString();
+ if (format != "rgba" && format != "rgb")
+ return Result("Invalid format specified to probe command");
+
+ if (format == "rgba")
+ cmd->SetIsRGBA();
+
+ token = tokenizer_->NextToken();
+ if (!cmd->IsWholeWindow()) {
+ bool got_rect_open_bracket = false;
+ if (token->IsOpenBracket()) {
+ got_rect_open_bracket = true;
+ token = tokenizer_->NextToken();
+ }
+
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetX(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ if (token->IsComma())
+ token = tokenizer_->NextToken();
+
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetY(token->AsFloat());
+
+ if (is_rect) {
+ token = tokenizer_->NextToken();
+ if (token->IsComma())
+ token = tokenizer_->NextToken();
+
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetWidth(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ if (token->IsComma())
+ token = tokenizer_->NextToken();
+
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetHeight(token->AsFloat());
+ }
+
+ token = tokenizer_->NextToken();
+ if (token->IsCloseBracket()) {
+ // Close bracket without an open
+ if (!got_rect_open_bracket)
+ return Result("Missing open bracket for probe command");
+
+ token = tokenizer_->NextToken();
+ } else if (got_rect_open_bracket) {
+ // An open bracket without a close bracket.
+ return Result("Missing close bracket for probe command");
+ }
+ }
+
+ bool got_color_open_bracket = false;
+ if (token->IsOpenBracket()) {
+ got_color_open_bracket = true;
+ token = tokenizer_->NextToken();
+ }
+
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetR(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ if (token->IsComma())
+ token = tokenizer_->NextToken();
+
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetG(token->AsFloat());
+
+ token = tokenizer_->NextToken();
+ if (token->IsComma())
+ token = tokenizer_->NextToken();
+
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetB(token->AsFloat());
+
+ if (format == "rgba") {
+ token = tokenizer_->NextToken();
+ if (token->IsComma())
+ token = tokenizer_->NextToken();
+
+ r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+ cmd->SetA(token->AsFloat());
+ }
+
+ token = tokenizer_->NextToken();
+ if (token->IsCloseBracket()) {
+ if (!got_color_open_bracket) {
+ // Close without an open.
+ return Result("Missing open bracket for probe command");
+ }
+ token = tokenizer_->NextToken();
+ } else if (got_color_open_bracket) {
+ // Open bracket without a close.
+ return Result("Missing close bracket for probe command");
+ }
+
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter to probe command");
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+Result CommandParser::ProcessTopology() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for topology command");
+ if (!token->IsString())
+ return Result("Invalid value for topology command");
+
+ Topology topology = Topology::kPatchList;
+ std::string topo = token->AsString();
+
+ if (topo == "VK_PRIMITIVE_TOPOLOGY_PATCH_LIST")
+ topology = Topology::kPatchList;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_POINT_LIST")
+ topology = Topology::kPointList;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_LINE_LIST")
+ topology = Topology::kLineList;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY")
+ topology = Topology::kLineListWithAdjacency;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_LINE_STRIP")
+ topology = Topology::kLineStrip;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY")
+ topology = Topology::kLineStripWithAdjacency;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN")
+ topology = Topology::kTriangleFan;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST")
+ topology = Topology::kTriangleList;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY")
+ topology = Topology::kTriangleListWithAdjacency;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP")
+ topology = Topology::kTriangleStrip;
+ else if (topo == "VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY")
+ topology = Topology::kTriangleStripWithAdjacency;
+ else
+ return Result("Unknown value for topology command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for topology command");
+
+ pipeline_data_.SetTopology(topology);
+ return {};
+}
+
+Result CommandParser::ProcessPolygonMode() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for polygonMode command");
+ if (!token->IsString())
+ return Result("Invalid value for polygonMode command");
+
+ PolygonMode mode = PolygonMode::kFill;
+ std::string m = token->AsString();
+ if (m == "VK_POLYGON_MODE_FILL")
+ mode = PolygonMode::kFill;
+ else if (m == "VK_POLYGON_MODE_LINE")
+ mode = PolygonMode::kLine;
+ else if (m == "VK_POLYGON_MODE_POINT")
+ mode = PolygonMode::kPoint;
+ else
+ return Result("Unknown value for polygonMode command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for polygonMode command");
+
+ pipeline_data_.SetPolygonMode(mode);
+ return {};
+}
+
+Result CommandParser::ProcessLogicOp() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for logicOp command");
+ if (!token->IsString())
+ return Result("Invalid value for logicOp command");
+
+ LogicOp op = LogicOp::kClear;
+ std::string name = token->AsString();
+ if (name == "VK_LOGIC_OP_CLEAR")
+ op = LogicOp::kClear;
+ else if (name == "VK_LOGIC_OP_AND")
+ op = LogicOp::kAnd;
+ else if (name == "VK_LOGIC_OP_AND_REVERSE")
+ op = LogicOp::kAndReverse;
+ else if (name == "VK_LOGIC_OP_COPY")
+ op = LogicOp::kCopy;
+ else if (name == "VK_LOGIC_OP_AND_INVERTED")
+ op = LogicOp::kAndInverted;
+ else if (name == "VK_LOGIC_OP_NO_OP")
+ op = LogicOp::kNoOp;
+ else if (name == "VK_LOGIC_OP_XOR")
+ op = LogicOp::kXor;
+ else if (name == "VK_LOGIC_OP_OR")
+ op = LogicOp::kOr;
+ else if (name == "VK_LOGIC_OP_NOR")
+ op = LogicOp::kNor;
+ else if (name == "VK_LOGIC_OP_EQUIVALENT")
+ op = LogicOp::kEquivalent;
+ else if (name == "VK_LOGIC_OP_INVERT")
+ op = LogicOp::kInvert;
+ else if (name == "VK_LOGIC_OP_OR_REVERSE")
+ op = LogicOp::kOrReverse;
+ else if (name == "VK_LOGIC_OP_COPY_INVERTED")
+ op = LogicOp::kCopyInverted;
+ else if (name == "VK_LOGIC_OP_OR_INVERTED")
+ op = LogicOp::kOrInverted;
+ else if (name == "VK_LOGIC_OP_NAND")
+ op = LogicOp::kNand;
+ else if (name == "VK_LOGIC_OP_SET")
+ op = LogicOp::kSet;
+ else
+ return Result("Unknown value for logicOp command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for logicOp command");
+
+ pipeline_data_.SetLogicOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessCullMode() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for cullMode command");
+ if (!token->IsString())
+ return Result("Invalid value for cullMode command");
+
+ CullMode mode = CullMode::kNone;
+ while (!token->IsEOS() && !token->IsEOL()) {
+ std::string name = token->AsString();
+
+ if (name == "|") {
+ // We treat everything as an |.
+ } else if (name == "VK_CULL_MODE_FRONT_BIT") {
+ if (mode == CullMode::kNone)
+ mode = CullMode::kFront;
+ else if (mode == CullMode::kBack)
+ mode = CullMode::kFrontAndBack;
+ } else if (name == "VK_CULL_MODE_BACK_BIT") {
+ if (mode == CullMode::kNone)
+ mode = CullMode::kBack;
+ else if (mode == CullMode::kFront)
+ mode = CullMode::kFrontAndBack;
+ } else if (name == "VK_CULL_MODE_FRONT_AND_BACK") {
+ mode = CullMode::kFrontAndBack;
+ } else if (name == "VK_CULL_MODE_NONE") {
+ // Do nothing ...
+ } else {
+ return Result("Unknown value for cullMode command");
+ }
+
+ token = tokenizer_->NextToken();
+ }
+
+ pipeline_data_.SetCullMode(mode);
+ return {};
+}
+
+Result CommandParser::ProcessFrontFace() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for frontFace command");
+ if (!token->IsString())
+ return Result("Invalid value for frontFace command");
+
+ FrontFace face = FrontFace::kCounterClockwise;
+ std::string f = token->AsString();
+ if (f == "VK_FRONT_FACE_COUNTER_CLOCKWISE")
+ face = FrontFace::kCounterClockwise;
+ else if (f == "VK_FRONT_FACE_CLOCKWISE")
+ face = FrontFace::kClockwise;
+ else
+ return Result("Unknown value for frontFace command");
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for frontFace command");
+
+ pipeline_data_.SetFrontFace(face);
+ return {};
+}
+
+Result CommandParser::ProcessBooleanPipelineData(const std::string& name,
+ bool* value) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for " + name + " command");
+ if (!token->IsString())
+ return Result("Invalid value for " + name + " command");
+
+ Result r = ParseBoolean(token->AsString(), value);
+ if (!r.IsSuccess())
+ return r;
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for " + name + " command");
+
+ return {};
+}
+
+Result CommandParser::ProcessPrimitiveRestartEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("primitiveRestartEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnablePrimitiveRestart(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthClampEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("depthClampEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableDepthClamp(value);
+ return {};
+}
+
+Result CommandParser::ProcessRasterizerDiscardEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("rasterizerDiscardEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableRasterizerDiscard(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthBiasEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("depthBiasEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableDepthBias(value);
+ return {};
+}
+
+Result CommandParser::ProcessLogicOpEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("logicOpEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableLogicOp(value);
+ return {};
+}
+
+Result CommandParser::ProcessBlendEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("blendEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableBlend(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthTestEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("depthTestEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableDepthTest(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthWriteEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("depthWriteEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableDepthWrite(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthBoundsTestEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("depthBoundsTestEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableDepthBoundsTest(value);
+ return {};
+}
+
+Result CommandParser::ProcessStencilTestEnable() {
+ bool value = false;
+ Result r = ProcessBooleanPipelineData("stencilTestEnable", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetEnableStencilTest(value);
+ return {};
+}
+
+Result CommandParser::ProcessFloatPipelineData(const std::string& name,
+ float* value) {
+ assert(value);
+
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing value for " + name + " command");
+
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+
+ *value = token->AsFloat();
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for " + name + " command");
+
+ return {};
+}
+
+Result CommandParser::ProcessDepthBiasConstantFactor() {
+ float value = 0.0;
+ Result r = ProcessFloatPipelineData("depthBiasConstantFactor", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetDepthBiasConstantFactor(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthBiasClamp() {
+ float value = 0.0;
+ Result r = ProcessFloatPipelineData("depthBiasClamp", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetDepthBiasClamp(value);
+ return {};
+}
+
+Result CommandParser::ProcessDepthBiasSlopeFactor() {
+ float value = 0.0;
+ Result r = ProcessFloatPipelineData("depthBiasSlopeFactor", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetDepthBiasSlopeFactor(value);
+ return {};
+}
+
+Result CommandParser::ProcessLineWidth() {
+ float value = 0.0;
+ Result r = ProcessFloatPipelineData("lineWidth", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetLineWidth(value);
+ return {};
+}
+
+Result CommandParser::ProcessMinDepthBounds() {
+ float value = 0.0;
+ Result r = ProcessFloatPipelineData("minDepthBounds", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetMinDepthBounds(value);
+ return {};
+}
+
+Result CommandParser::ProcessMaxDepthBounds() {
+ float value = 0.0;
+ Result r = ProcessFloatPipelineData("maxDepthBounds", &value);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetMaxDepthBounds(value);
+ return {};
+}
+
+Result CommandParser::ParseBlendFactorName(const std::string& name,
+ BlendFactor* factor) {
+ assert(factor);
+
+ if (name == "VK_BLEND_FACTOR_ZERO")
+ *factor = BlendFactor::kZero;
+ else if (name == "VK_BLEND_FACTOR_ONE")
+ *factor = BlendFactor::kOne;
+ else if (name == "VK_BLEND_FACTOR_SRC_COLOR")
+ *factor = BlendFactor::kSrcColor;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR")
+ *factor = BlendFactor::kOneMinusSrcColor;
+ else if (name == "VK_BLEND_FACTOR_DST_COLOR")
+ *factor = BlendFactor::kDstColor;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR")
+ *factor = BlendFactor::kOneMinusDstColor;
+ else if (name == "VK_BLEND_FACTOR_SRC_ALPHA")
+ *factor = BlendFactor::kSrcAlpha;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA")
+ *factor = BlendFactor::kOneMinusSrcAlpha;
+ else if (name == "VK_BLEND_FACTOR_DST_ALPHA")
+ *factor = BlendFactor::kDstAlpha;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA")
+ *factor = BlendFactor::kOneMinusDstAlpha;
+ else if (name == "VK_BLEND_FACTOR_CONSTANT_COLOR")
+ *factor = BlendFactor::kConstantColor;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR")
+ *factor = BlendFactor::kOneMinusConstantColor;
+ else if (name == "VK_BLEND_FACTOR_CONSTANT_ALPHA")
+ *factor = BlendFactor::kConstantAlpha;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA")
+ *factor = BlendFactor::kOneMinusConstantAlpha;
+ else if (name == "VK_BLEND_FACTOR_SRC_ALPHA_SATURATE")
+ *factor = BlendFactor::kSrcAlphaSaturate;
+ else if (name == "VK_BLEND_FACTOR_SRC1_COLOR")
+ *factor = BlendFactor::kSrc1Color;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR")
+ *factor = BlendFactor::kOneMinusSrc1Color;
+ else if (name == "VK_BLEND_FACTOR_SRC1_ALPHA")
+ *factor = BlendFactor::kSrc1Alpha;
+ else if (name == "VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA")
+ *factor = BlendFactor::kOneMinusSrc1Alpha;
+ else
+ return Result("Unknown BlendFactor provided: " + name);
+
+ return {};
+}
+
+Result CommandParser::ParseBlendFactor(const std::string& name,
+ BlendFactor* factor) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result(std::string("Missing parameter for ") + name + " command");
+ if (!token->IsString())
+ return Result(std::string("Invalid parameter for ") + name + " command");
+
+ Result r = ParseBlendFactorName(token->AsString(), factor);
+ if (!r.IsSuccess())
+ return r;
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result(std::string("Extra parameter for ") + name + " command");
+
+ return {};
+}
+
+Result CommandParser::ProcessSrcAlphaBlendFactor() {
+ BlendFactor factor = BlendFactor::kZero;
+ Result r = ParseBlendFactor("srcAlphaBlendFactor", &factor);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetSrcAlphaBlendFactor(factor);
+ return {};
+}
+
+Result CommandParser::ProcessDstAlphaBlendFactor() {
+ BlendFactor factor = BlendFactor::kZero;
+ Result r = ParseBlendFactor("dstAlphaBlendFactor", &factor);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetDstAlphaBlendFactor(factor);
+ return {};
+}
+
+Result CommandParser::ProcessSrcColorBlendFactor() {
+ BlendFactor factor = BlendFactor::kZero;
+ Result r = ParseBlendFactor("srcColorBlendFactor", &factor);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetSrcColorBlendFactor(factor);
+ return {};
+}
+
+Result CommandParser::ProcessDstColorBlendFactor() {
+ BlendFactor factor = BlendFactor::kZero;
+ Result r = ParseBlendFactor("dstColorBlendFactor", &factor);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetDstColorBlendFactor(factor);
+ return {};
+}
+
+Result CommandParser::ParseBlendOpName(const std::string& name, BlendOp* op) {
+ assert(op);
+
+ if (name == "VK_BLEND_OP_ADD")
+ *op = BlendOp::kAdd;
+ else if (name == "VK_BLEND_OP_ADD")
+ *op = BlendOp::kAdd;
+ else if (name == "VK_BLEND_OP_SUBTRACT")
+ *op = BlendOp::kSubtract;
+ else if (name == "VK_BLEND_OP_REVERSE_SUBTRACT")
+ *op = BlendOp::kReverseSubtract;
+ else if (name == "VK_BLEND_OP_MIN")
+ *op = BlendOp::kMin;
+ else if (name == "VK_BLEND_OP_MAX")
+ *op = BlendOp::kMax;
+ else if (name == "VK_BLEND_OP_ZERO_EXT")
+ *op = BlendOp::kZero;
+ else if (name == "VK_BLEND_OP_SRC_EXT")
+ *op = BlendOp::kSrc;
+ else if (name == "VK_BLEND_OP_DST_EXT")
+ *op = BlendOp::kDst;
+ else if (name == "VK_BLEND_OP_SRC_OVER_EXT")
+ *op = BlendOp::kSrcOver;
+ else if (name == "VK_BLEND_OP_DST_OVER_EXT")
+ *op = BlendOp::kDstOver;
+ else if (name == "VK_BLEND_OP_SRC_IN_EXT")
+ *op = BlendOp::kSrcIn;
+ else if (name == "VK_BLEND_OP_DST_IN_EXT")
+ *op = BlendOp::kDstIn;
+ else if (name == "VK_BLEND_OP_SRC_OUT_EXT")
+ *op = BlendOp::kSrcOut;
+ else if (name == "VK_BLEND_OP_DST_OUT_EXT")
+ *op = BlendOp::kDstOut;
+ else if (name == "VK_BLEND_OP_SRC_ATOP_EXT")
+ *op = BlendOp::kSrcAtop;
+ else if (name == "VK_BLEND_OP_DST_ATOP_EXT")
+ *op = BlendOp::kDstAtop;
+ else if (name == "VK_BLEND_OP_XOR_EXT")
+ *op = BlendOp::kXor;
+ else if (name == "VK_BLEND_OP_MULTIPLY_EXT")
+ *op = BlendOp::kMultiply;
+ else if (name == "VK_BLEND_OP_SCREEN_EXT")
+ *op = BlendOp::kScreen;
+ else if (name == "VK_BLEND_OP_OVERLAY_EXT")
+ *op = BlendOp::kOverlay;
+ else if (name == "VK_BLEND_OP_DARKEN_EXT")
+ *op = BlendOp::kDarken;
+ else if (name == "VK_BLEND_OP_LIGHTEN_EXT")
+ *op = BlendOp::kLighten;
+ else if (name == "VK_BLEND_OP_COLORDODGE_EXT")
+ *op = BlendOp::kColorDodge;
+ else if (name == "VK_BLEND_OP_COLORBURN_EXT")
+ *op = BlendOp::kColorBurn;
+ else if (name == "VK_BLEND_OP_HARDLIGHT_EXT")
+ *op = BlendOp::kHardLight;
+ else if (name == "VK_BLEND_OP_SOFTLIGHT_EXT")
+ *op = BlendOp::kSoftLight;
+ else if (name == "VK_BLEND_OP_DIFFERENCE_EXT")
+ *op = BlendOp::kDifference;
+ else if (name == "VK_BLEND_OP_EXCLUSION_EXT")
+ *op = BlendOp::kExclusion;
+ else if (name == "VK_BLEND_OP_INVERT_EXT")
+ *op = BlendOp::kInvert;
+ else if (name == "VK_BLEND_OP_INVERT_RGB_EXT")
+ *op = BlendOp::kInvertRGB;
+ else if (name == "VK_BLEND_OP_LINEARDODGE_EXT")
+ *op = BlendOp::kLinearDodge;
+ else if (name == "VK_BLEND_OP_LINEARBURN_EXT")
+ *op = BlendOp::kLinearBurn;
+ else if (name == "VK_BLEND_OP_VIVIDLIGHT_EXT")
+ *op = BlendOp::kVividLight;
+ else if (name == "VK_BLEND_OP_LINEARLIGHT_EXT")
+ *op = BlendOp::kLinearLight;
+ else if (name == "VK_BLEND_OP_PINLIGHT_EXT")
+ *op = BlendOp::kPinLight;
+ else if (name == "VK_BLEND_OP_HARDMIX_EXT")
+ *op = BlendOp::kHardMix;
+ else if (name == "VK_BLEND_OP_HSL_HUE_EXT")
+ *op = BlendOp::kHslHue;
+ else if (name == "VK_BLEND_OP_HSL_SATURATION_EXT")
+ *op = BlendOp::kHslSaturation;
+ else if (name == "VK_BLEND_OP_HSL_COLOR_EXT")
+ *op = BlendOp::kHslColor;
+ else if (name == "VK_BLEND_OP_HSL_LUMINOSITY_EXT")
+ *op = BlendOp::kHslLuminosity;
+ else if (name == "VK_BLEND_OP_PLUS_EXT")
+ *op = BlendOp::kPlus;
+ else if (name == "VK_BLEND_OP_PLUS_CLAMPED_EXT")
+ *op = BlendOp::kPlusClamped;
+ else if (name == "VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT")
+ *op = BlendOp::kPlusClampedAlpha;
+ else if (name == "VK_BLEND_OP_PLUS_DARKER_EXT")
+ *op = BlendOp::kPlusDarker;
+ else if (name == "VK_BLEND_OP_MINUS_EXT")
+ *op = BlendOp::kMinus;
+ else if (name == "VK_BLEND_OP_MINUS_CLAMPED_EXT")
+ *op = BlendOp::kMinusClamped;
+ else if (name == "VK_BLEND_OP_CONTRAST_EXT")
+ *op = BlendOp::kContrast;
+ else if (name == "VK_BLEND_OP_INVERT_OVG_EXT")
+ *op = BlendOp::kInvertOvg;
+ else if (name == "VK_BLEND_OP_RED_EXT")
+ *op = BlendOp::kRed;
+ else if (name == "VK_BLEND_OP_GREEN_EXT")
+ *op = BlendOp::kGreen;
+ else if (name == "VK_BLEND_OP_BLUE_EXT")
+ *op = BlendOp::kBlue;
+ else
+ return Result("Unknown BlendOp provided: " + name);
+
+ return {};
+}
+
+Result CommandParser::ParseBlendOp(const std::string& name, BlendOp* op) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result(std::string("Missing parameter for ") + name + " command");
+ if (!token->IsString())
+ return Result(std::string("Invalid parameter for ") + name + " command");
+
+ Result r = ParseBlendOpName(token->AsString(), op);
+ if (!r.IsSuccess())
+ return r;
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result(std::string("Extra parameter for ") + name + " command");
+
+ return {};
+}
+
+Result CommandParser::ProcessColorBlendOp() {
+ BlendOp op = BlendOp::kAdd;
+ Result r = ParseBlendOp("colorBlendOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetColorBlendOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessAlphaBlendOp() {
+ BlendOp op = BlendOp::kAdd;
+ Result r = ParseBlendOp("alphaBlendOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetAlphaBlendOp(op);
+ return {};
+}
+
+Result CommandParser::ParseCompareOp(const std::string& name, CompareOp* op) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result(std::string("Missing parameter for ") + name + " command");
+ if (!token->IsString())
+ return Result(std::string("Invalid parameter for ") + name + " command");
+
+ Result r = ParseCompareOpName(token->AsString(), op);
+ if (!r.IsSuccess())
+ return r;
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result(std::string("Extra parameter for ") + name + " command");
+
+ return {};
+}
+
+Result CommandParser::ParseCompareOpName(const std::string& name,
+ CompareOp* op) {
+ assert(op);
+
+ if (name == "VK_COMPARE_OP_NEVER")
+ *op = CompareOp::kNever;
+ else if (name == "VK_COMPARE_OP_LESS")
+ *op = CompareOp::kLess;
+ else if (name == "VK_COMPARE_OP_EQUAL")
+ *op = CompareOp::kEqual;
+ else if (name == "VK_COMPARE_OP_LESS_OR_EQUAL")
+ *op = CompareOp::kLessOrEqual;
+ else if (name == "VK_COMPARE_OP_GREATER")
+ *op = CompareOp::kGreater;
+ else if (name == "VK_COMPARE_OP_NOT_EQUAL")
+ *op = CompareOp::kNotEqual;
+ else if (name == "VK_COMPARE_OP_GREATER_OR_EQUAL")
+ *op = CompareOp::kGreaterOrEqual;
+ else if (name == "VK_COMPARE_OP_ALWAYS")
+ *op = CompareOp::kAlways;
+ else
+ return Result("Unknown CompareOp provided: " + name);
+
+ return {};
+}
+
+Result CommandParser::ProcessDepthCompareOp() {
+ CompareOp op = CompareOp::kNever;
+ Result r = ParseCompareOp("depthCompareOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetDepthCompareOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessFrontCompareOp() {
+ CompareOp op = CompareOp::kNever;
+ Result r = ParseCompareOp("front.compareOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetFrontCompareOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessBackCompareOp() {
+ CompareOp op = CompareOp::kNever;
+ Result r = ParseCompareOp("back.compareOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetBackCompareOp(op);
+ return {};
+}
+
+Result CommandParser::ParseStencilOp(const std::string& name, StencilOp* op) {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result(std::string("Missing parameter for ") + name + " command");
+ if (!token->IsString())
+ return Result(std::string("Invalid parameter for ") + name + " command");
+
+ Result r = ParseStencilOpName(token->AsString(), op);
+ if (!r.IsSuccess())
+ return r;
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result(std::string("Extra parameter for ") + name + " command");
+
+ return {};
+}
+
+Result CommandParser::ParseStencilOpName(const std::string& name,
+ StencilOp* op) {
+ assert(op);
+
+ if (name == "VK_STENCIL_OP_KEEP")
+ *op = StencilOp::kKeep;
+ else if (name == "VK_STENCIL_OP_ZERO")
+ *op = StencilOp::kZero;
+ else if (name == "VK_STENCIL_OP_REPLACE")
+ *op = StencilOp::kReplace;
+ else if (name == "VK_STENCIL_OP_INCREMENT_AND_CLAMP")
+ *op = StencilOp::kIncrementAndClamp;
+ else if (name == "VK_STENCIL_OP_DECREMENT_AND_CLAMP")
+ *op = StencilOp::kDecrementAndClamp;
+ else if (name == "VK_STENCIL_OP_INVERT")
+ *op = StencilOp::kInvert;
+ else if (name == "VK_STENCIL_OP_INCREMENT_AND_WRAP")
+ *op = StencilOp::kIncrementAndWrap;
+ else if (name == "VK_STENCIL_OP_DECREMENT_AND_WRAP")
+ *op = StencilOp::kDecrementAndWrap;
+ else
+ return Result("Unknown StencilOp provided: " + name);
+
+ return {};
+}
+
+Result CommandParser::ProcessFrontFailOp() {
+ StencilOp op = StencilOp::kKeep;
+ Result r = ParseStencilOp("front.failOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetFrontFailOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessFrontPassOp() {
+ StencilOp op = StencilOp::kKeep;
+ Result r = ParseStencilOp("front.passOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetFrontPassOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessFrontDepthFailOp() {
+ StencilOp op = StencilOp::kKeep;
+ Result r = ParseStencilOp("front.depthFailOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetFrontDepthFailOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessBackFailOp() {
+ StencilOp op = StencilOp::kKeep;
+ Result r = ParseStencilOp("back.failOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetBackFailOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessBackPassOp() {
+ StencilOp op = StencilOp::kKeep;
+ Result r = ParseStencilOp("back.passOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetBackPassOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessBackDepthFailOp() {
+ StencilOp op = StencilOp::kKeep;
+ Result r = ParseStencilOp("back.depthFailOp", &op);
+ if (!r.IsSuccess())
+ return r;
+
+ pipeline_data_.SetBackDepthFailOp(op);
+ return {};
+}
+
+Result CommandParser::ProcessFrontCompareMask() {
+ return Result("front.compareMask not implemented");
+}
+
+Result CommandParser::ProcessFrontWriteMask() {
+ return Result("front.writeMask not implemented");
+}
+
+Result CommandParser::ProcessBackCompareMask() {
+ return Result("back.compareMask not implemented");
+}
+
+Result CommandParser::ProcessBackWriteMask() {
+ return Result("back.writeMask not implemented");
+}
+
+Result CommandParser::ProcessFrontReference() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing parameter for front.reference command");
+ if (!token->IsInteger())
+ return Result("Invalid parameter for front.reference command");
+
+ pipeline_data_.SetFrontReference(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for front.reference command");
+
+ return {};
+}
+
+Result CommandParser::ProcessBackReference() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing parameter for back.reference command");
+ if (!token->IsInteger())
+ return Result("Invalid parameter for back.reference command");
+
+ pipeline_data_.SetBackReference(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Extra parameter for back.reference command");
+
+ return {};
+}
+
+Result CommandParser::ProcessColorWriteMask() {
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Missing parameter for colorWriteMask command");
+ if (!token->IsString())
+ return Result("Invalid parameter for colorWriteMask command");
+
+ uint8_t mask = 0;
+ while (!token->IsEOS() && !token->IsEOL()) {
+ std::string name = token->AsString();
+
+ if (name == "|") {
+ // We treat everything as an |.
+ } else if (name == "VK_COLOR_COMPONENT_R_BIT") {
+ mask |= kColorMaskR;
+ } else if (name == "VK_COLOR_COMPONENT_G_BIT") {
+ mask |= kColorMaskG;
+ } else if (name == "VK_COLOR_COMPONENT_B_BIT") {
+ mask |= kColorMaskB;
+ } else if (name == "VK_COLOR_COMPONENT_A_BIT") {
+ mask |= kColorMaskA;
+ } else {
+ return Result("Unknown parameter for colorWriteMask command");
+ }
+
+ token = tokenizer_->NextToken();
+ }
+
+ pipeline_data_.SetColorWriteMask(mask);
+ return {};
+}
+
+Result CommandParser::ParseComparator(const std::string& name,
+ ProbeSSBOCommand::Comparator* op) {
+ if (name == "==")
+ *op = ProbeSSBOCommand::Comparator::kEqual;
+ else if (name == "!=")
+ *op = ProbeSSBOCommand::Comparator::kNotEqual;
+ else if (name == "~=")
+ *op = ProbeSSBOCommand::Comparator::kFuzzyEqual;
+ else if (name == "<")
+ *op = ProbeSSBOCommand::Comparator::kLess;
+ else if (name == "<=")
+ *op = ProbeSSBOCommand::Comparator::kLessOrEqual;
+ else if (name == ">")
+ *op = ProbeSSBOCommand::Comparator::kGreater;
+ else if (name == ">=")
+ *op = ProbeSSBOCommand::Comparator::kGreaterOrEqual;
+ else
+ return Result("Invalid comparator");
+ return {};
+}
+
+Result CommandParser::ProcessProbeSSBO() {
+ auto cmd = MakeUnique<ProbeSSBOCommand>();
+
+ auto token = tokenizer_->NextToken();
+ if (token->IsEOL() || token->IsEOS())
+ return Result("Missing values for probe ssbo command");
+ if (!token->IsString())
+ return Result("Invalid type for probe ssbo command");
+
+ DatumTypeParser tp;
+ Result r = tp.Parse(token->AsString());
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetDatumType(tp.GetType());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsInteger())
+ return Result("Invalid binding value for probe ssbo command");
+
+ uint32_t val = token->AsUint32();
+
+ token = tokenizer_->NextToken();
+ if (token->IsString()) {
+ auto& str = token->AsString();
+ if (str.size() >= 2 && str[0] == ':') {
+ cmd->SetDescriptorSet(val);
+
+ auto substr = str.substr(1, str.size());
+ uint64_t binding_val = strtoul(substr.c_str(), nullptr, 10);
+ if (binding_val > std::numeric_limits<uint32_t>::max())
+ return Result("binding value too large in probe ssbo command");
+
+ cmd->SetBinding(static_cast<uint32_t>(binding_val));
+ } else {
+ return Result("Invalid value for probe ssbo command");
+ }
+
+ token = tokenizer_->NextToken();
+ } else {
+ cmd->SetBinding(val);
+ }
+
+ if (!token->IsInteger())
+ return Result("Invalid offset for probe ssbo command");
+
+ cmd->SetOffset(token->AsUint32());
+
+ token = tokenizer_->NextToken();
+ if (!token->IsString())
+ return Result("Invalid comparator for probe ssbo command");
+
+ ProbeSSBOCommand::Comparator comp;
+ r = ParseComparator(token->AsString(), &comp);
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetComparator(comp);
+
+ std::vector<Value> values;
+ r = ParseValues("probe ssbo", cmd->GetDatumType(), &values);
+ if (!r.IsSuccess())
+ return r;
+
+ cmd->SetValues(std::move(values));
+
+ commands_.push_back(std::move(cmd));
+ return {};
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/command_parser.h b/src/vkscript/command_parser.h
new file mode 100644
index 0000000..2ded530
--- /dev/null
+++ b/src/vkscript/command_parser.h
@@ -0,0 +1,159 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_COMMAND_PARSER_H_
+#define SRC_VKSCRIPT_COMMAND_PARSER_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/command.h"
+#include "src/datum_type.h"
+#include "src/pipeline_data.h"
+
+namespace amber {
+
+class Tokenizer;
+class Token;
+
+namespace vkscript {
+
+class CommandParser {
+ public:
+ CommandParser();
+ ~CommandParser();
+
+ Result Parse(const std::string& data);
+
+ void AddCommand(std::unique_ptr<Command> command) {
+ commands_.push_back(std::move(command));
+ }
+
+ const std::vector<std::unique_ptr<Command>>& Commands() const {
+ return commands_;
+ }
+
+ std::vector<std::unique_ptr<Command>>&& TakeCommands() {
+ return std::move(commands_);
+ }
+
+ const PipelineData* PipelineDataForTesting() const { return &pipeline_data_; }
+
+ Result ParseBooleanForTesting(const std::string& str, bool* result) {
+ return ParseBoolean(str, result);
+ }
+
+ Result ParseBlendFactorNameForTesting(const std::string& name,
+ BlendFactor* factor) {
+ return ParseBlendFactorName(name, factor);
+ }
+ Result ParseBlendOpNameForTesting(const std::string& name, BlendOp* op) {
+ return ParseBlendOpName(name, op);
+ }
+ Result ParseCompareOpNameForTesting(const std::string& name, CompareOp* op) {
+ return ParseCompareOpName(name, op);
+ }
+ Result ParseStencilOpNameForTesting(const std::string& name, StencilOp* op) {
+ return ParseStencilOpName(name, op);
+ }
+ Result ParseComparatorForTesting(const std::string& name,
+ ProbeSSBOCommand::Comparator* op) {
+ return ParseComparator(name, op);
+ }
+
+ private:
+ Result TokenToFloat(Token* token, float* val) const;
+ Result TokenToDouble(Token* token, double* val) const;
+ Result ParseBoolean(const std::string& str, bool* result);
+ Result ParseValues(const std::string& name,
+ const DatumType& type,
+ std::vector<Value>* values);
+
+ Result ProcessDrawRect();
+ Result ProcessDrawArrays();
+ Result ProcessCompute();
+ Result ProcessClear();
+ Result ProcessPatch();
+ Result ProcessSSBO();
+ Result ProcessUniform();
+ Result ProcessTolerance();
+ Result ProcessEntryPoint(const std::string& name);
+ Result ProcessProbe(bool relative);
+ Result ProcessProbeSSBO();
+ Result ProcessTopology();
+ Result ProcessPolygonMode();
+ Result ProcessLogicOp();
+ Result ProcessCullMode();
+ Result ProcessFrontFace();
+ Result ProcessFloatPipelineData(const std::string& name, float* value);
+ Result ProcessDepthBiasConstantFactor();
+ Result ProcessDepthBiasClamp();
+ Result ProcessDepthBiasSlopeFactor();
+ Result ProcessLineWidth();
+ Result ProcessMinDepthBounds();
+ Result ProcessMaxDepthBounds();
+ Result ProcessBooleanPipelineData(const std::string& name, bool* value);
+ Result ProcessPrimitiveRestartEnable();
+ Result ProcessDepthClampEnable();
+ Result ProcessRasterizerDiscardEnable();
+ Result ProcessDepthBiasEnable();
+ Result ProcessLogicOpEnable();
+ Result ProcessBlendEnable();
+ Result ProcessDepthTestEnable();
+ Result ProcessDepthWriteEnable();
+ Result ProcessDepthBoundsTestEnable();
+ Result ProcessStencilTestEnable();
+ Result ParseBlendFactor(const std::string& name, BlendFactor* factor);
+ Result ParseBlendFactorName(const std::string& name, BlendFactor* factor);
+ Result ProcessSrcAlphaBlendFactor();
+ Result ProcessDstAlphaBlendFactor();
+ Result ProcessSrcColorBlendFactor();
+ Result ProcessDstColorBlendFactor();
+ Result ParseBlendOp(const std::string& name, BlendOp* op);
+ Result ParseBlendOpName(const std::string& name, BlendOp* op);
+ Result ProcessColorBlendOp();
+ Result ProcessAlphaBlendOp();
+ Result ParseCompareOp(const std::string& name, CompareOp* op);
+ Result ParseCompareOpName(const std::string& name, CompareOp* op);
+ Result ProcessDepthCompareOp();
+ Result ProcessFrontCompareOp();
+ Result ProcessBackCompareOp();
+ Result ParseStencilOp(const std::string& name, StencilOp* op);
+ Result ParseStencilOpName(const std::string& name, StencilOp* op);
+ Result ProcessFrontFailOp();
+ Result ProcessFrontPassOp();
+ Result ProcessFrontDepthFailOp();
+ Result ProcessBackFailOp();
+ Result ProcessBackPassOp();
+ Result ProcessBackDepthFailOp();
+ Result ProcessFrontCompareMask();
+ Result ProcessFrontWriteMask();
+ Result ProcessBackCompareMask();
+ Result ProcessBackWriteMask();
+ Result ProcessFrontReference();
+ Result ProcessBackReference();
+ Result ProcessColorWriteMask();
+ Result ParseComparator(const std::string& name,
+ ProbeSSBOCommand::Comparator* op);
+
+ PipelineData pipeline_data_;
+ std::unique_ptr<Tokenizer> tokenizer_;
+ std::vector<std::unique_ptr<Command>> commands_;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_COMMAND_PARSER_H_
diff --git a/src/vkscript/command_parser_test.cc b/src/vkscript/command_parser_test.cc
new file mode 100644
index 0000000..214350c
--- /dev/null
+++ b/src/vkscript/command_parser_test.cc
@@ -0,0 +1,3488 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/command_parser.h"
+#include "gtest/gtest.h"
+#include "src/vkscript/section_parser.h"
+
+namespace amber {
+namespace vkscript {
+
+using CommandParserTest = testing::Test;
+
+TEST_F(CommandParserTest, MultipleCommands) {
+ std::string data = R"(# this is the test data
+draw rect 1.2 2.3 200 400.2
+# another comment
+clear color 255 128 1 100 # set clear color
+clear
+# done)";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(3U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawRect());
+
+ auto* draw_cmd = cmds[0]->AsDrawRect();
+ EXPECT_FALSE(draw_cmd->IsOrtho());
+ EXPECT_FALSE(draw_cmd->IsPatch());
+ EXPECT_FLOAT_EQ(1.2, draw_cmd->GetX());
+ EXPECT_FLOAT_EQ(2.3, draw_cmd->GetY());
+ EXPECT_FLOAT_EQ(200, draw_cmd->GetWidth());
+ EXPECT_FLOAT_EQ(400.2, draw_cmd->GetHeight());
+
+ ASSERT_TRUE(cmds[1]->IsClearColor());
+
+ auto* clear_cmd = cmds[1]->AsClearColor();
+ EXPECT_EQ(255, clear_cmd->GetR());
+ EXPECT_EQ(128, clear_cmd->GetG());
+ EXPECT_EQ(1, clear_cmd->GetB());
+ EXPECT_EQ(100, clear_cmd->GetA());
+
+ ASSERT_TRUE(cmds[2]->IsClear());
+}
+
+TEST_F(CommandParserTest, DISABLED_DrawArraysNonInstancedFollowedByCommand) {}
+
+TEST_F(CommandParserTest, DISABLED_DrawArraysInstancedFollowedByCommand) {}
+
+TEST_F(CommandParserTest, DISABLED_UnknownCommand) {}
+
+TEST_F(CommandParserTest, DrawRect) {
+ std::string data = "draw rect 1.2 2.3 200 400.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawRect());
+
+ auto* cmd = cmds[0]->AsDrawRect();
+ EXPECT_FALSE(cmd->IsOrtho());
+ EXPECT_FALSE(cmd->IsPatch());
+ EXPECT_FLOAT_EQ(1.2, cmd->GetX());
+ EXPECT_FLOAT_EQ(2.3, cmd->GetY());
+ EXPECT_FLOAT_EQ(200, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(400.2, cmd->GetHeight());
+}
+
+TEST_F(CommandParserTest, DrawRectWithOrth) {
+ std::string data = "draw rect ortho 1.2 2.3 200 400.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawRect());
+
+ auto* cmd = cmds[0]->AsDrawRect();
+ EXPECT_TRUE(cmd->IsOrtho());
+ EXPECT_FALSE(cmd->IsPatch());
+ EXPECT_FLOAT_EQ(1.2, cmd->GetX());
+ EXPECT_FLOAT_EQ(2.3, cmd->GetY());
+ EXPECT_FLOAT_EQ(200, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(400.2, cmd->GetHeight());
+}
+
+TEST_F(CommandParserTest, DrawRectWithPatch) {
+ std::string data = "draw rect patch 1.2 2.3 200 400.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawRect());
+
+ auto* cmd = cmds[0]->AsDrawRect();
+ EXPECT_FALSE(cmd->IsOrtho());
+ EXPECT_TRUE(cmd->IsPatch());
+ EXPECT_FLOAT_EQ(1.2, cmd->GetX());
+ EXPECT_FLOAT_EQ(2.3, cmd->GetY());
+ EXPECT_FLOAT_EQ(200, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(400.2, cmd->GetHeight());
+}
+
+TEST_F(CommandParserTest, DrawRectWithOrthAndPatch) {
+ std::string data = "draw rect ortho patch 1.2 2.3 200 400.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawRect());
+
+ auto* cmd = cmds[0]->AsDrawRect();
+ EXPECT_TRUE(cmd->IsOrtho());
+ EXPECT_TRUE(cmd->IsPatch());
+ EXPECT_FLOAT_EQ(1.2, cmd->GetX());
+ EXPECT_FLOAT_EQ(2.3, cmd->GetY());
+ EXPECT_FLOAT_EQ(200, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(400.2, cmd->GetHeight());
+}
+
+TEST_F(CommandParserTest, DrawRectTooShort) {
+ std::string data = "draw rect 1.2 2.3 400.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawRectExtraParameters) {
+ std::string data = "draw rect ortho patch 1.2 2.3 200 400.2 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to draw rect command", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawArrays) {
+ std::string data = "draw arrays GL_LINES 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawArrays());
+
+ auto* cmd = cmds[0]->AsDrawArrays();
+ EXPECT_FALSE(cmd->IsIndexed());
+ EXPECT_FALSE(cmd->IsInstanced());
+ EXPECT_EQ(static_cast<uint32_t>(0U), cmd->GetInstanceCount());
+ EXPECT_EQ(Topology::kLineList, cmd->GetTopology());
+ EXPECT_EQ(2U, cmd->GetFirstVertexIndex());
+ EXPECT_EQ(4U, cmd->GetVertexCount());
+}
+
+TEST_F(CommandParserTest, DrawArraysIndexed) {
+ std::string data = "draw arrays indexed TRIANGLE_FAN 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawArrays());
+
+ auto* cmd = cmds[0]->AsDrawArrays();
+ EXPECT_TRUE(cmd->IsIndexed());
+ EXPECT_FALSE(cmd->IsInstanced());
+ EXPECT_EQ(static_cast<uint32_t>(0U), cmd->GetInstanceCount());
+ EXPECT_EQ(Topology::kTriangleFan, cmd->GetTopology());
+ EXPECT_EQ(2U, cmd->GetFirstVertexIndex());
+ EXPECT_EQ(4U, cmd->GetVertexCount());
+}
+
+TEST_F(CommandParserTest, DrawArraysExtraParams) {
+ std::string data = "draw arrays indexed TRIANGLE_FAN 2 4 EXTRA_PARAM";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to draw arrays command", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawArraysInstanced) {
+ std::string data = "draw arrays instanced LINE_LIST_WITH_ADJACENCY 2 9";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawArrays());
+
+ auto* cmd = cmds[0]->AsDrawArrays();
+ EXPECT_FALSE(cmd->IsIndexed());
+ EXPECT_TRUE(cmd->IsInstanced());
+ EXPECT_EQ(static_cast<uint32_t>(0U), cmd->GetInstanceCount());
+ EXPECT_EQ(Topology::kLineListWithAdjacency, cmd->GetTopology());
+ EXPECT_EQ(2U, cmd->GetFirstVertexIndex());
+ EXPECT_EQ(9U, cmd->GetVertexCount());
+}
+
+TEST_F(CommandParserTest, DrawArraysInstancedExtraParams) {
+ std::string data =
+ "draw arrays instanced LINE_LIST_WITH_ADJACENCY 2 9 4 EXTRA_COMMAND";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to draw arrays command", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawArraysIndexedAndInstanced) {
+ std::string data =
+ "draw arrays indexed instanced LINE_LIST_WITH_ADJACENCY 3 9";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawArrays());
+
+ auto* cmd = cmds[0]->AsDrawArrays();
+ EXPECT_TRUE(cmd->IsIndexed());
+ EXPECT_TRUE(cmd->IsInstanced());
+ EXPECT_EQ(static_cast<uint32_t>(0U), cmd->GetInstanceCount());
+ EXPECT_EQ(Topology::kLineListWithAdjacency, cmd->GetTopology());
+ EXPECT_EQ(3U, cmd->GetFirstVertexIndex());
+ EXPECT_EQ(9U, cmd->GetVertexCount());
+}
+
+TEST_F(CommandParserTest, DrawArraysInstancedWithCount) {
+ std::string data = "draw arrays instanced LINE_LIST_WITH_ADJACENCY 3 9 12";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsDrawArrays());
+
+ auto* cmd = cmds[0]->AsDrawArrays();
+ EXPECT_FALSE(cmd->IsIndexed());
+ EXPECT_TRUE(cmd->IsInstanced());
+ EXPECT_EQ(12U, cmd->GetInstanceCount());
+ EXPECT_EQ(Topology::kLineListWithAdjacency, cmd->GetTopology());
+ EXPECT_EQ(3U, cmd->GetFirstVertexIndex());
+ EXPECT_EQ(9U, cmd->GetVertexCount());
+}
+
+TEST_F(CommandParserTest, DrawArraysBadTopology) {
+ std::string data = "draw arrays UNKNOWN_TOPO 1 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown parameter to draw arrays: UNKNOWN_TOPO", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawArraysTooShort) {
+ std::string data = "draw arrays PATCH_LIST 1";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing integer vertex count value for draw arrays", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawArraysInstanceCountWithoutInstanced) {
+ std::string data = "draw arrays PATCH_LIST 1 2 3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to draw arrays command", r.Error());
+}
+
+TEST_F(CommandParserTest, DrawArraysMissingTopology) {
+ std::string data = "draw arrays 1 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing draw arrays topology", r.Error());
+}
+
+TEST_F(CommandParserTest, Compute) {
+ std::string data = "compute 1 2 3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsCompute());
+
+ auto* cmd = cmds[0]->AsCompute();
+ EXPECT_EQ(1U, cmd->GetX());
+ EXPECT_EQ(2U, cmd->GetY());
+ EXPECT_EQ(3U, cmd->GetZ());
+}
+
+TEST_F(CommandParserTest, ComputeTooShort) {
+ std::string data = "compute 1 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing integer value for compute Z entry", r.Error());
+}
+
+TEST_F(CommandParserTest, ComputeInvalidX) {
+ std::string data = "compute 1.2 2 3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing integer value for compute X entry", r.Error());
+}
+
+TEST_F(CommandParserTest, ComputeInvalidY) {
+ std::string data = "compute 1 a 3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing integer value for compute Y entry", r.Error());
+}
+
+TEST_F(CommandParserTest, ComputeInvalidZ) {
+ std::string data = "compute 1 2 1.5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing integer value for compute Z entry", r.Error());
+}
+
+TEST_F(CommandParserTest, ComputeExtraCommands) {
+ std::string data = "compute 1 2 3 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to compute command", r.Error());
+}
+
+TEST_F(CommandParserTest, Clear) {
+ std::string data = "clear";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsClear());
+}
+
+TEST_F(CommandParserTest, ClearExtraParams) {
+ std::string data = "clear EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to clear command", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearDepth) {
+ std::string data = "clear depth 0.8";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsClearDepth());
+
+ auto* cmd = cmds[0]->AsClearDepth();
+ EXPECT_FLOAT_EQ(0.8, cmd->GetValue());
+}
+
+TEST_F(CommandParserTest, ClearDepthMissingValue) {
+ std::string data = "clear depth";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearDepthExtraParameters) {
+ std::string data = "clear depth 0.2 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to clear depth command", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearStencil) {
+ std::string data = "clear stencil 8";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsClearStencil());
+
+ auto* cmd = cmds[0]->AsClearStencil();
+ EXPECT_FLOAT_EQ(8, cmd->GetValue());
+}
+
+TEST_F(CommandParserTest, ClearStencilMissingValue) {
+ std::string data = "clear stencil";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing stencil value for clear stencil command", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearStencilExtraParameters) {
+ std::string data = "clear stencil 2 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to clear stencil command", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearStencilNotInteger) {
+ std::string data = "clear stencil 2.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid stencil value for clear stencil command", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearColor) {
+ std::string data = "clear color 0.8 0.4 0.2 1.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsClearColor());
+
+ auto* cmd = cmds[0]->AsClearColor();
+ EXPECT_FLOAT_EQ(0.8, cmd->GetR());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetG());
+ EXPECT_FLOAT_EQ(0.2, cmd->GetB());
+ EXPECT_FLOAT_EQ(1.3, cmd->GetA());
+}
+
+TEST_F(CommandParserTest, ClearColorMissingParams) {
+ std::string data = "clear color 0.8 0.4 0.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearColorExtraParams) {
+ std::string data = "clear color 0.8 0.4 0.2 1.3 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter to clear color command", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearColorBadR) {
+ std::string data = "clear color a 0.4 0.2 0.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearColorBadG) {
+ std::string data = "clear color 0.2 a 0.2 0.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearColorBadB) {
+ std::string data = "clear color 0.2 0.4 a 0.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, ClearColorBadA) {
+ std::string data = "clear color 0.2 0.4 0.2 a";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_F(CommandParserTest, PatchParameterVertices) {
+ std::string data = "patch parameter vertices 9";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsPatchParameterVertices());
+
+ auto* cmd = cmds[0]->AsPatchParameterVertices();
+ EXPECT_FLOAT_EQ(9U, cmd->GetControlPointCount());
+}
+
+TEST_F(CommandParserTest, PatchParameterVerticesMissingParameter) {
+ std::string data = "patch vertices 5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing parameter flag to patch command", r.Error());
+}
+
+TEST_F(CommandParserTest, PatchParameterVerticesMissingVertices) {
+ std::string data = "patch parameter 5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing vertices flag to patch command", r.Error());
+}
+
+TEST_F(CommandParserTest, PatchParameterVerticesMissingParam) {
+ std::string data = "patch parameter vertices";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid count parameter for patch parameter vertices", r.Error());
+}
+
+TEST_F(CommandParserTest, PatchParameterVerticesInvalidParam) {
+ std::string data = "patch parameter vertices invalid";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid count parameter for patch parameter vertices", r.Error());
+}
+
+TEST_F(CommandParserTest, PatchParameterVerticesExtraParam) {
+ std::string data = "patch parameter vertices 3 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter for patch parameter vertices command", r.Error());
+}
+
+struct EntryInfo {
+ const char* name;
+ ShaderType type;
+};
+static const EntryInfo kEntryPoints[] = {
+ {"vertex", ShaderType::kVertex},
+ {"fragment", ShaderType::kFragment},
+ {"geometry", ShaderType::kGeometry},
+ {"compute", ShaderType::kCompute},
+ {"tessellation evaluation", ShaderType::kTessellationEvaluation},
+ {"tessellation control", ShaderType::kTessellationControl},
+};
+
+TEST_F(CommandParserTest, EntryPoint) {
+ for (const auto& ep : kEntryPoints) {
+ std::string data = std::string(ep.name) + " entrypoint main";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsEntryPoint());
+
+ auto* cmd = cmds[0]->AsEntryPoint();
+ EXPECT_EQ(ep.type, cmd->GetShaderType());
+ EXPECT_EQ("main", cmd->GetEntryPointName());
+ }
+}
+
+TEST_F(CommandParserTest, EntryPointNameMissing) {
+ for (const auto& ep : kEntryPoints) {
+ std::string data = std::string(ep.name) + " entrypoint";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing entrypoint name", r.Error());
+ }
+}
+
+TEST_F(CommandParserTest, EntryPointEntryPointMissing) {
+ for (const auto& ep : kEntryPoints) {
+ // Skip compute because compute is also a command ....
+ if (std::string(ep.name) == "compute")
+ continue;
+
+ std::string data = std::string(ep.name) + " main";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown command: " + std::string(ep.name), r.Error());
+ }
+}
+
+TEST_F(CommandParserTest, EntryPointExtraParam) {
+ for (const auto& ep : kEntryPoints) {
+ std::string data = std::string(ep.name) + " entrypoint main EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter for entrypoint command", r.Error());
+ }
+}
+
+TEST_F(CommandParserTest, EntryPointInvalidValue) {
+ for (const auto& ep : kEntryPoints) {
+ std::string data = std::string(ep.name) + " entrypoint 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Entrypoint name must be a string", r.Error());
+ }
+}
+
+TEST_F(CommandParserTest, TessellationEntryPointRequiresASuffix) {
+ std::string data = "tessellation entrypoint main";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Tessellation entrypoint must have <evaluation|control> in name",
+ r.Error());
+}
+
+TEST_F(CommandParserTest, TessellationEntryPointRequiresAKnownSuffix) {
+ std::string data = "tessellation unknown entrypoint main";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Tessellation entrypoint must have <evaluation|control> in name",
+ r.Error());
+}
+
+TEST_F(CommandParserTest, InvalidEntryPoint) {
+ std::string data = "unknown entrypoint main";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown command: unknown", r.Error());
+}
+
+using CommandParserProbeTest = testing::TestWithParam<bool>;
+
+TEST_P(CommandParserProbeTest, ProbeRgb) {
+ bool is_relative = GetParam();
+
+ std::string data = (is_relative ? std::string("relative ") : std::string()) +
+ "probe rgb 25 30 0.2 0.4 0.6";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << data << std::endl << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_EQ(is_relative, cmd->IsRelative());
+ EXPECT_FALSE(cmd->IsWholeWindow());
+ EXPECT_FALSE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(25U, cmd->GetX());
+ EXPECT_FLOAT_EQ(30U, cmd->GetY());
+ EXPECT_FLOAT_EQ(1U, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(1U, cmd->GetHeight());
+
+ EXPECT_FLOAT_EQ(0.2, cmd->GetR());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetG());
+ EXPECT_FLOAT_EQ(0.6, cmd->GetB());
+}
+
+TEST_P(CommandParserProbeTest, ProbeRgba) {
+ bool is_relative = GetParam();
+
+ std::string data = (is_relative ? std::string("relative ") : std::string()) +
+ "probe rgba 25 30 1 255 9 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << data << std::endl << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_EQ(is_relative, cmd->IsRelative());
+ EXPECT_FALSE(cmd->IsWholeWindow());
+ EXPECT_TRUE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(25U, cmd->GetX());
+ EXPECT_FLOAT_EQ(30U, cmd->GetY());
+ EXPECT_FLOAT_EQ(1U, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(1U, cmd->GetHeight());
+
+ EXPECT_FLOAT_EQ(1, cmd->GetR());
+ EXPECT_FLOAT_EQ(255, cmd->GetG());
+ EXPECT_FLOAT_EQ(9, cmd->GetB());
+ EXPECT_FLOAT_EQ(4, cmd->GetA());
+}
+
+TEST_P(CommandParserProbeTest, ProbeRect) {
+ bool is_relative = GetParam();
+
+ std::string data = (is_relative ? std::string("relative ") : std::string()) +
+ "probe rect rgba 25 30 200 400 1 255 9 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << data << std::endl << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_EQ(is_relative, cmd->IsRelative());
+ EXPECT_FALSE(cmd->IsWholeWindow());
+ EXPECT_TRUE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(25U, cmd->GetX());
+ EXPECT_FLOAT_EQ(30U, cmd->GetY());
+ EXPECT_FLOAT_EQ(200U, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(400U, cmd->GetHeight());
+
+ EXPECT_FLOAT_EQ(1, cmd->GetR());
+ EXPECT_FLOAT_EQ(255, cmd->GetG());
+ EXPECT_FLOAT_EQ(9, cmd->GetB());
+ EXPECT_FLOAT_EQ(4, cmd->GetA());
+}
+
+INSTANTIATE_TEST_CASE_P(ProbeTests,
+ CommandParserProbeTest,
+ testing::Values(false, true), );
+
+TEST_F(CommandParserTest, ProbeAllRGB) {
+ std::string data = "probe all rgb 0.2 0.3 0.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_FALSE(cmd->IsRelative());
+ EXPECT_TRUE(cmd->IsWholeWindow());
+ EXPECT_FALSE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(0.2, cmd->GetR());
+ EXPECT_FLOAT_EQ(0.3, cmd->GetG());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetB());
+}
+
+TEST_F(CommandParserTest, ProbeAllRGBA) {
+ std::string data = "probe all rgba 0.2 0.3 0.4 0.5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_FALSE(cmd->IsRelative());
+ EXPECT_TRUE(cmd->IsWholeWindow());
+ EXPECT_TRUE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(0.2, cmd->GetR());
+ EXPECT_FLOAT_EQ(0.3, cmd->GetG());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetB());
+ EXPECT_FLOAT_EQ(0.5, cmd->GetA());
+}
+
+TEST_F(CommandParserTest, ProbeCommandRectBrackets) {
+ std::string data = "relative probe rect rgb (0.5, 0.6, 0.3, 0.4) 1 2 3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_TRUE(cmd->IsRelative());
+ EXPECT_FALSE(cmd->IsWholeWindow());
+ EXPECT_FALSE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(0.5, cmd->GetX());
+ EXPECT_FLOAT_EQ(0.6, cmd->GetY());
+ EXPECT_FLOAT_EQ(0.3, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetHeight());
+
+ EXPECT_FLOAT_EQ(1.0, cmd->GetR());
+ EXPECT_FLOAT_EQ(2.0, cmd->GetG());
+ EXPECT_FLOAT_EQ(3.0, cmd->GetB());
+}
+
+TEST_F(CommandParserTest, ProbeCommandColorBrackets) {
+ std::string data = "relative probe rect rgb 0.5 0.6 0.3 0.4 (1, 2, 3)";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_TRUE(cmd->IsRelative());
+ EXPECT_FALSE(cmd->IsWholeWindow());
+ EXPECT_FALSE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(0.5, cmd->GetX());
+ EXPECT_FLOAT_EQ(0.6, cmd->GetY());
+ EXPECT_FLOAT_EQ(0.3, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetHeight());
+
+ EXPECT_FLOAT_EQ(1.0, cmd->GetR());
+ EXPECT_FLOAT_EQ(2.0, cmd->GetG());
+ EXPECT_FLOAT_EQ(3.0, cmd->GetB());
+}
+
+TEST_F(CommandParserTest, ProbeCommandColorOptionalCommas) {
+ std::string data = "relative probe rect rgb 0.5, 0.6, 0.3 0.4 1 2 3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbe());
+
+ auto* cmd = cmds[0]->AsProbe();
+ EXPECT_TRUE(cmd->IsRelative());
+ EXPECT_FALSE(cmd->IsWholeWindow());
+ EXPECT_FALSE(cmd->IsRGBA());
+
+ EXPECT_FLOAT_EQ(0.5, cmd->GetX());
+ EXPECT_FLOAT_EQ(0.6, cmd->GetY());
+ EXPECT_FLOAT_EQ(0.3, cmd->GetWidth());
+ EXPECT_FLOAT_EQ(0.4, cmd->GetHeight());
+
+ EXPECT_FLOAT_EQ(1.0, cmd->GetR());
+ EXPECT_FLOAT_EQ(2.0, cmd->GetG());
+ EXPECT_FLOAT_EQ(3.0, cmd->GetB());
+}
+
+TEST_F(CommandParserTest, ProbeErrors) {
+ struct {
+ const char* str;
+ const char* err;
+ } probes[] = {
+ {"probe rgba ab 30 0.2 0.3 0.4 0.5", "Invalid conversion to double"},
+ {"relative probe rgba ab 30 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"probe rect rgba ab 30 2 3 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba ab 30 2 3 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rgba 30 ab 0.2 0.3 0.4 0.5", "Invalid conversion to double"},
+ {"relative probe rgba 30 ab 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"probe rect rgba 30 ab 2 3 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 30 ab 2 3 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rect rgba 30 40 ab 3 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 30 40 ab 3 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rect rgba 30 40 3 ab 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 30 40 3 ab 0.2 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rgba 10 30 ab 0.3 0.4 0.5", "Invalid conversion to double"},
+ {"relative probe rgba 10 30 ab 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"probe rect rgba 10 30 2 3 ab 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 10 30 2 3 ab 0.3 0.4 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rgba 10 30 0.2 ab 0.4 0.5", "Invalid conversion to double"},
+ {"relative probe rgba 10 30 0.2 ab 0.4 0.5",
+ "Invalid conversion to double"},
+ {"probe rect rgba 10 30 2 3 0.2 ab 0.4 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 10 30 2 3 0.2 ab 0.4 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rgba 10 30 0.2 0.3 ab 0.5", "Invalid conversion to double"},
+ {"relative probe rgba 10 30 0.2 0.3 ab 0.5",
+ "Invalid conversion to double"},
+ {"probe rect rgba 10 30 2 3 0.2 0.3 ab 0.5",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 10 30 2 3 0.2 0.3 ab 0.5",
+ "Invalid conversion to double"},
+
+ {"probe rgba 10 30 0.2 0.3 0.4 ab", "Invalid conversion to double"},
+ {"relative probe rgba 10 30 0.2 0.3 0.4 ab",
+ "Invalid conversion to double"},
+ {"probe rect rgba 10 30 2 3 0.2 0.3 0.4 ab",
+ "Invalid conversion to double"},
+ {"relative probe rect rgba 10 30 2 3 0.2 0.3 0.4 ab",
+ "Invalid conversion to double"},
+
+ {"probe all rgb ab 2 3", "Invalid conversion to double"},
+ {"probe all rgb 2 ab 4", "Invalid conversion to double"},
+ {"probe all rgb 2 3 ab", "Invalid conversion to double"},
+
+ {"probe all rgba ab 2 3 4", "Invalid conversion to double"},
+ {"probe all rgba 2 ab 4 5", "Invalid conversion to double"},
+ {"probe all rgba 2 3 ab 5", "Invalid conversion to double"},
+ {"probe all rgba 2 3 4 ab", "Invalid conversion to double"},
+
+ {"probe rgb 10 30 0.2 0.3 0.4 extra", "Extra parameter to probe command"},
+ {"probe rgba 10 30 0.2 0.3 0.4 0.4 extra",
+ "Extra parameter to probe command"},
+ {"relative probe rgb 10 30 0.2 0.3 0.4 extra",
+ "Extra parameter to probe command"},
+ {"relative probe rgba 10 30 0.2 0.3 0.4 0.4 extra",
+ "Extra parameter to probe command"},
+ {"probe rect rgb 10 30 40 50 0.2 0.3 0.4 extra",
+ "Extra parameter to probe command"},
+ {"probe rect rgba 10 30 40 50 0.2 0.3 0.4 0.4 extra",
+ "Extra parameter to probe command"},
+ {"relative probe rect rgb 10 30 40 50 0.2 0.3 0.4 extra",
+ "Extra parameter to probe command"},
+ {"relative probe rect rgba 10 30 40 50 0.2 0.3 0.4 0.4 extra",
+ "Extra parameter to probe command"},
+ {"probe all rgb 2 3 4 EXTRA", "Extra parameter to probe command"},
+ {"probe all rgba 2 3 4 5 EXTRA", "Extra parameter to probe command"},
+
+ {"relative probe rect rgb 0.5 0.6 0.3 0.4 1 2 3)",
+ "Missing open bracket for probe command"},
+ {"relative probe rect rgb (0.5 0.6 0.3 0.4 1 2 3",
+ "Missing close bracket for probe command"},
+ {"relative probe rect rgb 0.5 0.6 0.3 0.4) 1 2 3",
+ "Missing open bracket for probe command"},
+ {"relative probe rect rgb 0.5 0.6 0.3 0.4 (1, 2, 3",
+ "Missing close bracket for probe command"},
+ {"relative probe rect rgb (0.5, 0.6, 0.3, 0.4, 1, 2, 3)",
+ "Missing close bracket for probe command"},
+ };
+
+ for (const auto& probe : probes) {
+ CommandParser cp;
+ Result r = cp.Parse(probe.str);
+ EXPECT_FALSE(r.IsSuccess()) << probe.str;
+ EXPECT_EQ(probe.err, r.Error()) << probe.str;
+ }
+}
+
+TEST_F(CommandParserTest, RelativeWithoutProbe) {
+ std::string data = "relative unknown";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("relative must be used with probe", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeWithInvalidRGBA) {
+ std::string data = "probe 1";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid token in probe command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeWithRectAndInvalidRGB) {
+ std::string data = "probe rect 1";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid token in probe command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeWithRectMissingFormat) {
+ std::string data = "probe rect unknown";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid format specified to probe command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeAllMissingFormat) {
+ std::string data = "probe all unknown";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid format specified to probe command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeAlWithInvalidRGB) {
+ std::string data = "probe all unknown";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid format specified to probe command", r.Error());
+}
+
+struct TopologyTestData {
+ const char* name;
+ Topology value;
+};
+using CommandDataPipelineTopologyParser =
+ testing::TestWithParam<TopologyTestData>;
+
+TEST_P(CommandDataPipelineTopologyParser, Topology) {
+ const auto& test_data = GetParam();
+
+ std::string data = "topology " + std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.value, cp.PipelineDataForTesting()->GetTopology());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ TopologyTests,
+ CommandDataPipelineTopologyParser,
+ testing::Values(
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_PATCH_LIST",
+ Topology::kPatchList},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_POINT_LIST",
+ Topology::kPointList},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_LINE_LIST",
+ Topology::kLineList},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY",
+ Topology::kLineListWithAdjacency},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_LINE_STRIP",
+ Topology::kLineStrip},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY",
+ Topology::kLineStripWithAdjacency},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN",
+ Topology::kTriangleFan},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST",
+ Topology::kTriangleList},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY",
+ Topology::kTriangleListWithAdjacency},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP",
+ Topology::kTriangleStrip},
+ TopologyTestData{"VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY",
+ Topology::kTriangleStripWithAdjacency}), );
+
+struct PipelineDataInvalidTest {
+ const char* name;
+ const char* arg;
+};
+using CommandDataPipelineDataInvalidParser =
+ testing::TestWithParam<PipelineDataInvalidTest>;
+
+TEST_P(CommandDataPipelineDataInvalidParser, InvalidPipelineParamValue) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid value for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandDataPipelineDataInvalidParser, MissingTopologyValue) {
+ const auto& test_data = GetParam();
+
+ std::string data = test_data.name;
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing value for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandDataPipelineDataInvalidParser, UnknownPipelineParamValue) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " UNKNOWN";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Unknown value for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandDataPipelineDataInvalidParser, ExtraPipelineParamValue) {
+ const auto& test_data = GetParam();
+
+ // CullMode consumes all parameters, so skip this test.
+ if (std::string(test_data.name) == "cullMode")
+ return;
+
+ std::string data =
+ std::string(test_data.name) + " " + test_data.arg + " EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ PipelineDataInvalidTests,
+ CommandDataPipelineDataInvalidParser,
+ testing::Values(
+ PipelineDataInvalidTest{"topology", "VK_PRIMITIVE_TOPOLOGY_POINT_LIST"},
+ PipelineDataInvalidTest{"polygonMode", "VK_POLYGON_MODE_POINT"},
+ PipelineDataInvalidTest{"cullMode", "VK_CULL_MODE_BACK_BIT"},
+ PipelineDataInvalidTest{"frontFace", "VK_FRONT_FACE_COUNTER_CLOCKWISE"},
+ PipelineDataInvalidTest{"logicOp", "VK_LOGIC_OP_NO_OP"}), );
+
+TEST_F(CommandParserTest, BooleanTrue) {
+ struct {
+ const char* name;
+ } data[] = {{"TRUE"}, {"true"}, {"TRuE"}};
+
+ for (const auto& d : data) {
+ CommandParser cp;
+
+ bool value = false;
+ Result r = cp.ParseBooleanForTesting(d.name, &value);
+ EXPECT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(value);
+ }
+}
+
+TEST_F(CommandParserTest, BooleanFalse) {
+ struct {
+ const char* name;
+ } data[] = {{"FALSE"}, {"false"}, {"FAlsE"}};
+
+ for (const auto& d : data) {
+ CommandParser cp;
+
+ bool value = true;
+ Result r = cp.ParseBooleanForTesting(d.name, &value);
+ EXPECT_TRUE(r.IsSuccess()) << d.name << " " << r.Error();
+ EXPECT_FALSE(value);
+ }
+}
+
+TEST_F(CommandParserTest, BooleanInvalid) {
+ struct {
+ const char* name;
+ } data[] = {{""}, {"Invalid"}};
+
+ for (const auto& d : data) {
+ CommandParser cp;
+
+ bool value = true;
+ Result r = cp.ParseBooleanForTesting(d.name, &value);
+ ASSERT_FALSE(r.IsSuccess()) << d.name;
+ EXPECT_EQ("Invalid value passed as a boolean string", r.Error());
+ }
+}
+
+TEST_F(CommandParserTest, PrimitiveRestartEnable) {
+ std::string data = "primitiveRestartEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnablePrimitiveRestart());
+}
+
+TEST_F(CommandParserTest, PrimitiveRestartDisable) {
+ std::string data = "primitiveRestartEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnablePrimitiveRestart());
+}
+
+TEST_F(CommandParserTest, DepthClampEnable) {
+ std::string data = "depthClampEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableDepthClamp());
+}
+
+TEST_F(CommandParserTest, DepthClampDisable) {
+ std::string data = "depthClampEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableDepthClamp());
+}
+
+TEST_F(CommandParserTest, RasterizerDiscardEnable) {
+ std::string data = "rasterizerDiscardEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableRasterizerDiscard());
+}
+
+TEST_F(CommandParserTest, RasterizerDiscardDisable) {
+ std::string data = "rasterizerDiscardEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableRasterizerDiscard());
+}
+
+TEST_F(CommandParserTest, DepthBiasEnable) {
+ std::string data = "depthBiasEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableDepthBias());
+}
+
+TEST_F(CommandParserTest, DepthBiasDisable) {
+ std::string data = "depthBiasEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableDepthBias());
+}
+
+TEST_F(CommandParserTest, LogicOpEnable) {
+ std::string data = "logicOpEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableLogicOp());
+}
+
+TEST_F(CommandParserTest, LogicOpDisable) {
+ std::string data = "logicOpEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableLogicOp());
+}
+
+TEST_F(CommandParserTest, BlendEnable) {
+ std::string data = "blendEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableBlend());
+}
+
+TEST_F(CommandParserTest, BlendDisable) {
+ std::string data = "blendEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableBlend());
+}
+
+TEST_F(CommandParserTest, DepthTestEnable) {
+ std::string data = "depthTestEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableDepthTest());
+}
+
+TEST_F(CommandParserTest, DepthTestDisable) {
+ std::string data = "depthTestEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableDepthTest());
+}
+
+TEST_F(CommandParserTest, DepthWriteEnable) {
+ std::string data = "depthWriteEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableDepthWrite());
+}
+
+TEST_F(CommandParserTest, DepthWriteDisable) {
+ std::string data = "depthWriteEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableDepthWrite());
+}
+
+TEST_F(CommandParserTest, DepthBoundsTestEnable) {
+ std::string data = "depthBoundsTestEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableDepthBoundsTest());
+}
+
+TEST_F(CommandParserTest, DepthBoundsTestDisable) {
+ std::string data = "depthBoundsTestEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableDepthBoundsTest());
+}
+
+TEST_F(CommandParserTest, StencilTestEnable) {
+ std::string data = "stencilTestEnable true";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_TRUE(cp.PipelineDataForTesting()->GetEnableStencilTest());
+}
+
+TEST_F(CommandParserTest, StencilTestDisable) {
+ std::string data = "stencilTestEnable false";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FALSE(cp.PipelineDataForTesting()->GetEnableStencilTest());
+}
+
+struct BooleanTest {
+ const char* name;
+};
+using CommandParserBooleanTests = testing::TestWithParam<BooleanTest>;
+
+TEST_P(CommandParserBooleanTests, MissingParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing value for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserBooleanTests, IllegalParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid value for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserBooleanTests, ExtraParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " true EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(BooleanTests,
+ CommandParserBooleanTests,
+ testing::Values(BooleanTest{"primitiveRestartEnable"},
+ BooleanTest{"depthClampEnable"},
+ BooleanTest{"rasterizerDiscardEnable"},
+ BooleanTest{"depthBiasEnable"},
+ BooleanTest{"logicOpEnable"},
+ BooleanTest{"blendEnable"},
+ BooleanTest{"depthTestEnable"},
+ BooleanTest{"depthWriteEnable"},
+ BooleanTest{"depthBoundsTestEnable"},
+ BooleanTest{"stencilTestEnable"}), );
+
+struct PolygonModeTestData {
+ const char* name;
+ PolygonMode value;
+};
+using CommandDataPipelinePolygonModeParser =
+ testing::TestWithParam<PolygonModeTestData>;
+
+TEST_P(CommandDataPipelinePolygonModeParser, PolygonMode) {
+ const auto& test_data = GetParam();
+
+ std::string data = "polygonMode " + std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.value, cp.PipelineDataForTesting()->GetPolygonMode());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ PolygonModeTests,
+ CommandDataPipelinePolygonModeParser,
+ testing::Values(
+ PolygonModeTestData{"VK_POLYGON_MODE_FILL", PolygonMode::kFill},
+ PolygonModeTestData{"VK_POLYGON_MODE_LINE", PolygonMode::kLine},
+ PolygonModeTestData{"VK_POLYGON_MODE_POINT", PolygonMode::kPoint}), );
+
+struct CullModeTestData {
+ const char* name;
+ CullMode value;
+};
+using CommandDataPipelineCullModeParser =
+ testing::TestWithParam<CullModeTestData>;
+
+TEST_P(CommandDataPipelineCullModeParser, CullMode) {
+ const auto& test_data = GetParam();
+
+ std::string data = "cullMode " + std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.value, cp.PipelineDataForTesting()->GetCullMode());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ CullModeTests,
+ CommandDataPipelineCullModeParser,
+ testing::Values(
+ CullModeTestData{"VK_CULL_MODE_NONE", CullMode::kNone},
+ CullModeTestData{"VK_CULL_MODE_FRONT_BIT", CullMode::kFront},
+ CullModeTestData{"VK_CULL_MODE_BACK_BIT", CullMode::kBack},
+ CullModeTestData{"VK_CULL_MODE_BACK_BIT | VK_CULL_MODE_FRONT_BIT",
+ CullMode::kFrontAndBack},
+ CullModeTestData{"VK_CULL_MODE_FRONT_BIT | VK_CULL_MODE_BACK_BIT",
+ CullMode::kFrontAndBack},
+ CullModeTestData{"VK_CULL_MODE_FRONT_AND_BACK",
+ CullMode::kFrontAndBack}), );
+
+struct FrontFaceTestData {
+ const char* name;
+ FrontFace value;
+};
+using CommandDataPipelineFrontFaceParser =
+ testing::TestWithParam<FrontFaceTestData>;
+
+TEST_P(CommandDataPipelineFrontFaceParser, FrontFace) {
+ const auto& test_data = GetParam();
+
+ std::string data = "frontFace " + std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.value, cp.PipelineDataForTesting()->GetFrontFace());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ FrontFaceTests,
+ CommandDataPipelineFrontFaceParser,
+ testing::Values(FrontFaceTestData{"VK_FRONT_FACE_COUNTER_CLOCKWISE",
+ FrontFace::kCounterClockwise},
+ FrontFaceTestData{"VK_FRONT_FACE_CLOCKWISE",
+ FrontFace::kClockwise}), );
+
+struct LogicOpTestData {
+ const char* name;
+ LogicOp value;
+};
+using CommandDataPipelineLogicOpParser =
+ testing::TestWithParam<LogicOpTestData>;
+
+TEST_P(CommandDataPipelineLogicOpParser, LogicOp) {
+ const auto& test_data = GetParam();
+
+ std::string data = "logicOp " + std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.value, cp.PipelineDataForTesting()->GetLogicOp());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ LogicOpTests,
+ CommandDataPipelineLogicOpParser,
+ testing::Values(
+ LogicOpTestData{"VK_LOGIC_OP_CLEAR", LogicOp::kClear},
+ LogicOpTestData{"VK_LOGIC_OP_AND", LogicOp::kAnd},
+ LogicOpTestData{"VK_LOGIC_OP_AND_REVERSE", LogicOp::kAndReverse},
+ LogicOpTestData{"VK_LOGIC_OP_COPY", LogicOp::kCopy},
+ LogicOpTestData{"VK_LOGIC_OP_AND_INVERTED", LogicOp::kAndInverted},
+ LogicOpTestData{"VK_LOGIC_OP_NO_OP", LogicOp::kNoOp},
+ LogicOpTestData{"VK_LOGIC_OP_XOR", LogicOp::kXor},
+ LogicOpTestData{"VK_LOGIC_OP_OR", LogicOp::kOr},
+ LogicOpTestData{"VK_LOGIC_OP_NOR", LogicOp::kNor},
+ LogicOpTestData{"VK_LOGIC_OP_EQUIVALENT", LogicOp::kEquivalent},
+ LogicOpTestData{"VK_LOGIC_OP_INVERT", LogicOp::kInvert},
+ LogicOpTestData{"VK_LOGIC_OP_OR_REVERSE", LogicOp::kOrReverse},
+ LogicOpTestData{"VK_LOGIC_OP_COPY_INVERTED", LogicOp::kCopyInverted},
+ LogicOpTestData{"VK_LOGIC_OP_OR_INVERTED", LogicOp::kOrInverted},
+ LogicOpTestData{"VK_LOGIC_OP_NAND", LogicOp::kNand},
+ LogicOpTestData{"VK_LOGIC_OP_SET", LogicOp::kSet}), );
+
+TEST_F(CommandParserTest, DepthBiasConstantFactor) {
+ std::string data = "depthBiasConstantFactor 3.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FLOAT_EQ(3.4,
+ cp.PipelineDataForTesting()->GetDepthBiasConstantFactor());
+}
+
+TEST_F(CommandParserTest, DepthBiasClamp) {
+ std::string data = "depthBiasClamp 3.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FLOAT_EQ(3.4, cp.PipelineDataForTesting()->GetDepthBiasClamp());
+}
+
+TEST_F(CommandParserTest, DepthBiasSlopeFactor) {
+ std::string data = "depthBiasSlopeFactor 3.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FLOAT_EQ(3.4, cp.PipelineDataForTesting()->GetDepthBiasSlopeFactor());
+}
+
+TEST_F(CommandParserTest, LineWidth) {
+ std::string data = "lineWidth 3.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FLOAT_EQ(3.4, cp.PipelineDataForTesting()->GetLineWidth());
+}
+
+TEST_F(CommandParserTest, MinDepthBounds) {
+ std::string data = "minDepthBounds 3.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FLOAT_EQ(3.4, cp.PipelineDataForTesting()->GetMinDepthBounds());
+}
+
+TEST_F(CommandParserTest, MaxDepthBounds) {
+ std::string data = "maxDepthBounds 3.4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_FLOAT_EQ(3.4, cp.PipelineDataForTesting()->GetMaxDepthBounds());
+}
+
+struct FloatTest {
+ const char* name;
+};
+using CommandParserFloatTests = testing::TestWithParam<FloatTest>;
+
+TEST_P(CommandParserFloatTests, MissingParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing value for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserFloatTests, IllegalParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid conversion to double", r.Error());
+}
+
+TEST_P(CommandParserFloatTests, ExtraParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 3.2 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(FloatTests,
+ CommandParserFloatTests,
+ testing::Values(FloatTest{"depthBiasConstantFactor"},
+ FloatTest{"lineWidth"},
+ FloatTest{"depthBiasClamp"},
+ FloatTest{"depthBiasSlopeFactor"},
+ FloatTest{"minDepthBounds"},
+ FloatTest{"maxDepthBounds"}), );
+
+TEST_F(CommandParserTest, SrcColorBlendFactor) {
+ std::string data = "srcColorBlendFactor VK_BLEND_FACTOR_DST_COLOR";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(BlendFactor::kDstColor,
+ cp.PipelineDataForTesting()->GetSrcColorBlendFactor());
+}
+
+TEST_F(CommandParserTest, DstColorBlendFactor) {
+ std::string data = "dstColorBlendFactor VK_BLEND_FACTOR_DST_COLOR";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(BlendFactor::kDstColor,
+ cp.PipelineDataForTesting()->GetDstColorBlendFactor());
+}
+
+TEST_F(CommandParserTest, SrcAlphaBlendFactor) {
+ std::string data = "srcAlphaBlendFactor VK_BLEND_FACTOR_DST_COLOR";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(BlendFactor::kDstColor,
+ cp.PipelineDataForTesting()->GetSrcAlphaBlendFactor());
+}
+
+TEST_F(CommandParserTest, DstAlphaBlendFactor) {
+ std::string data = "dstAlphaBlendFactor VK_BLEND_FACTOR_DST_COLOR";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(BlendFactor::kDstColor,
+ cp.PipelineDataForTesting()->GetDstAlphaBlendFactor());
+}
+
+struct BlendFactorData {
+ const char* name;
+ BlendFactor type;
+};
+using CommandParserBlendFactorParsing = testing::TestWithParam<BlendFactorData>;
+
+TEST_P(CommandParserBlendFactorParsing, Parse) {
+ const auto& test_data = GetParam();
+
+ CommandParser cp;
+ BlendFactor factor = BlendFactor::kZero;
+ Result r = cp.ParseBlendFactorNameForTesting(test_data.name, &factor);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.type, factor);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ BlendFactorParsingTests,
+ CommandParserBlendFactorParsing,
+ testing::Values(
+ BlendFactorData{"VK_BLEND_FACTOR_ZERO", BlendFactor::kZero},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE", BlendFactor::kOne},
+ BlendFactorData{"VK_BLEND_FACTOR_SRC_COLOR", BlendFactor::kSrcColor},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR",
+ BlendFactor::kOneMinusSrcColor},
+ BlendFactorData{"VK_BLEND_FACTOR_DST_COLOR", BlendFactor::kDstColor},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR",
+ BlendFactor::kOneMinusDstColor},
+ BlendFactorData{"VK_BLEND_FACTOR_SRC_ALPHA", BlendFactor::kSrcAlpha},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA",
+ BlendFactor::kOneMinusSrcAlpha},
+ BlendFactorData{"VK_BLEND_FACTOR_DST_ALPHA", BlendFactor::kDstAlpha},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA",
+ BlendFactor::kOneMinusDstAlpha},
+ BlendFactorData{"VK_BLEND_FACTOR_CONSTANT_COLOR",
+ BlendFactor::kConstantColor},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR",
+ BlendFactor::kOneMinusConstantColor},
+ BlendFactorData{"VK_BLEND_FACTOR_CONSTANT_ALPHA",
+ BlendFactor::kConstantAlpha},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA",
+ BlendFactor::kOneMinusConstantAlpha},
+ BlendFactorData{"VK_BLEND_FACTOR_SRC_ALPHA_SATURATE",
+ BlendFactor::kSrcAlphaSaturate},
+ BlendFactorData{"VK_BLEND_FACTOR_SRC1_COLOR", BlendFactor::kSrc1Color},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_SRC1_COLOR",
+ BlendFactor::kOneMinusSrc1Color},
+ BlendFactorData{"VK_BLEND_FACTOR_SRC1_ALPHA", BlendFactor::kSrc1Alpha},
+ BlendFactorData{"VK_BLEND_FACTOR_ONE_MINUS_SRC1_ALPHA",
+ BlendFactor::kOneMinusSrc1Alpha}), );
+
+TEST_F(CommandParserTest, BlendFactorParsingInvalid) {
+ CommandParser cp;
+ BlendFactor factor = BlendFactor::kZero;
+ Result r = cp.ParseBlendFactorNameForTesting("INVALID", &factor);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown BlendFactor provided: INVALID", r.Error());
+}
+
+struct BlendFactorTest {
+ const char* name;
+};
+using CommandParserBlendFactorTests = testing::TestWithParam<BlendFactorTest>;
+
+TEST_P(CommandParserBlendFactorTests, MissingParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserBlendFactorTests, IllegalParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 1.23";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserBlendFactorTests, ExtraParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " VK_BLEND_FACTOR_ONE EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(BlendFactorTests,
+ CommandParserBlendFactorTests,
+ testing::Values(BlendFactorTest{"srcColorBlendFactor"},
+ BlendFactorTest{"dstColorBlendFactor"},
+ BlendFactorTest{"srcAlphaBlendFactor"},
+ BlendFactorTest{
+ "dstAlphaBlendFactor"}), );
+
+TEST_F(CommandParserTest, ColorBlendOp) {
+ std::string data = "colorBlendOp VK_BLEND_OP_XOR_EXT";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(BlendOp::kXor, cp.PipelineDataForTesting()->GetColorBlendOp());
+}
+
+TEST_F(CommandParserTest, AlphaBlendOp) {
+ std::string data = "alphaBlendOp VK_BLEND_OP_XOR_EXT";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(BlendOp::kXor, cp.PipelineDataForTesting()->GetAlphaBlendOp());
+}
+
+struct BlendOpData {
+ const char* name;
+ BlendOp type;
+};
+using CommandParserBlendOpParsing = testing::TestWithParam<BlendOpData>;
+
+TEST_P(CommandParserBlendOpParsing, Parse) {
+ const auto& test_data = GetParam();
+
+ CommandParser cp;
+ BlendOp op = BlendOp::kAdd;
+ Result r = cp.ParseBlendOpNameForTesting(test_data.name, &op);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.type, op);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ BlendOpParsingTests1,
+ CommandParserBlendOpParsing,
+ testing::Values(
+ BlendOpData{"VK_BLEND_OP_ADD", BlendOp::kAdd},
+ BlendOpData{"VK_BLEND_OP_SUBTRACT", BlendOp::kSubtract},
+ BlendOpData{"VK_BLEND_OP_REVERSE_SUBTRACT", BlendOp::kReverseSubtract},
+ BlendOpData{"VK_BLEND_OP_MIN", BlendOp::kMin},
+ BlendOpData{"VK_BLEND_OP_MAX", BlendOp::kMax},
+ BlendOpData{"VK_BLEND_OP_ZERO_EXT", BlendOp::kZero},
+ BlendOpData{"VK_BLEND_OP_SRC_EXT", BlendOp::kSrc},
+ BlendOpData{"VK_BLEND_OP_DST_EXT", BlendOp::kDst},
+ BlendOpData{"VK_BLEND_OP_SRC_OVER_EXT", BlendOp::kSrcOver},
+ BlendOpData{"VK_BLEND_OP_DST_OVER_EXT", BlendOp::kDstOver},
+ BlendOpData{"VK_BLEND_OP_SRC_IN_EXT", BlendOp::kSrcIn},
+ BlendOpData{"VK_BLEND_OP_DST_IN_EXT", BlendOp::kDstIn},
+ BlendOpData{"VK_BLEND_OP_SRC_OUT_EXT", BlendOp::kSrcOut},
+ BlendOpData{"VK_BLEND_OP_DST_OUT_EXT", BlendOp::kDstOut},
+ BlendOpData{"VK_BLEND_OP_SRC_ATOP_EXT", BlendOp::kSrcAtop},
+ BlendOpData{"VK_BLEND_OP_DST_ATOP_EXT", BlendOp::kDstAtop},
+ BlendOpData{"VK_BLEND_OP_XOR_EXT", BlendOp::kXor},
+ BlendOpData{"VK_BLEND_OP_MULTIPLY_EXT", BlendOp::kMultiply},
+ BlendOpData{"VK_BLEND_OP_SCREEN_EXT", BlendOp::kScreen},
+ BlendOpData{"VK_BLEND_OP_OVERLAY_EXT", BlendOp::kOverlay},
+ BlendOpData{"VK_BLEND_OP_DARKEN_EXT", BlendOp::kDarken},
+ BlendOpData{"VK_BLEND_OP_LIGHTEN_EXT", BlendOp::kLighten},
+ BlendOpData{"VK_BLEND_OP_COLORDODGE_EXT", BlendOp::kColorDodge},
+ BlendOpData{"VK_BLEND_OP_COLORBURN_EXT", BlendOp::kColorBurn},
+ BlendOpData{"VK_BLEND_OP_HARDLIGHT_EXT", BlendOp::kHardLight},
+ BlendOpData{"VK_BLEND_OP_SOFTLIGHT_EXT", BlendOp::kSoftLight},
+ BlendOpData{"VK_BLEND_OP_DIFFERENCE_EXT", BlendOp::kDifference},
+ BlendOpData{"VK_BLEND_OP_EXCLUSION_EXT", BlendOp::kExclusion},
+ BlendOpData{"VK_BLEND_OP_INVERT_EXT", BlendOp::kInvert}), );
+
+INSTANTIATE_TEST_CASE_P(
+ BlendOpParsingTests2,
+ CommandParserBlendOpParsing,
+ testing::Values(
+ BlendOpData{"VK_BLEND_OP_INVERT_RGB_EXT", BlendOp::kInvertRGB},
+ BlendOpData{"VK_BLEND_OP_LINEARDODGE_EXT", BlendOp::kLinearDodge},
+ BlendOpData{"VK_BLEND_OP_LINEARBURN_EXT", BlendOp::kLinearBurn},
+ BlendOpData{"VK_BLEND_OP_VIVIDLIGHT_EXT", BlendOp::kVividLight},
+ BlendOpData{"VK_BLEND_OP_LINEARLIGHT_EXT", BlendOp::kLinearLight},
+ BlendOpData{"VK_BLEND_OP_PINLIGHT_EXT", BlendOp::kPinLight},
+ BlendOpData{"VK_BLEND_OP_HARDMIX_EXT", BlendOp::kHardMix},
+ BlendOpData{"VK_BLEND_OP_HSL_HUE_EXT", BlendOp::kHslHue},
+ BlendOpData{"VK_BLEND_OP_HSL_SATURATION_EXT", BlendOp::kHslSaturation},
+ BlendOpData{"VK_BLEND_OP_HSL_COLOR_EXT", BlendOp::kHslColor},
+ BlendOpData{"VK_BLEND_OP_HSL_LUMINOSITY_EXT", BlendOp::kHslLuminosity},
+ BlendOpData{"VK_BLEND_OP_PLUS_EXT", BlendOp::kPlus},
+ BlendOpData{"VK_BLEND_OP_PLUS_CLAMPED_EXT", BlendOp::kPlusClamped},
+ BlendOpData{"VK_BLEND_OP_PLUS_CLAMPED_ALPHA_EXT",
+ BlendOp::kPlusClampedAlpha},
+ BlendOpData{"VK_BLEND_OP_PLUS_DARKER_EXT", BlendOp::kPlusDarker},
+ BlendOpData{"VK_BLEND_OP_MINUS_EXT", BlendOp::kMinus},
+ BlendOpData{"VK_BLEND_OP_MINUS_CLAMPED_EXT", BlendOp::kMinusClamped},
+ BlendOpData{"VK_BLEND_OP_CONTRAST_EXT", BlendOp::kContrast},
+ BlendOpData{"VK_BLEND_OP_INVERT_OVG_EXT", BlendOp::kInvertOvg},
+ BlendOpData{"VK_BLEND_OP_RED_EXT", BlendOp::kRed},
+ BlendOpData{"VK_BLEND_OP_GREEN_EXT", BlendOp::kGreen},
+ BlendOpData{"VK_BLEND_OP_BLUE_EXT", BlendOp::kBlue}), );
+
+TEST_F(CommandParserTest, BlendOpParsingInvalid) {
+ CommandParser cp;
+ BlendOp op = BlendOp::kAdd;
+ Result r = cp.ParseBlendOpNameForTesting("INVALID", &op);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown BlendOp provided: INVALID", r.Error());
+}
+
+struct BlendOpTest {
+ const char* name;
+};
+using CommandParserBlendOpTests = testing::TestWithParam<BlendOpTest>;
+
+TEST_P(CommandParserBlendOpTests, MissingParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserBlendOpTests, IllegalParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 1.23";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserBlendOpTests, ExtraParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " VK_BLEND_OP_MAX EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(BlendOpTests,
+ CommandParserBlendOpTests,
+ testing::Values(BlendOpTest{"colorBlendOp"},
+ BlendOpTest{"alphaBlendOp"}), );
+
+TEST_F(CommandParserTest, DepthCompareOp) {
+ std::string data = "depthCompareOp VK_COMPARE_OP_EQUAL";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(CompareOp::kEqual,
+ cp.PipelineDataForTesting()->GetDepthCompareOp());
+}
+
+TEST_F(CommandParserTest, FrontCompareOp) {
+ std::string data = "front.compareOp VK_COMPARE_OP_EQUAL";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(CompareOp::kEqual,
+ cp.PipelineDataForTesting()->GetFrontCompareOp());
+}
+
+TEST_F(CommandParserTest, BackCompareOp) {
+ std::string data = "back.compareOp VK_COMPARE_OP_EQUAL";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(CompareOp::kEqual, cp.PipelineDataForTesting()->GetBackCompareOp());
+}
+
+struct CompareOpData {
+ const char* name;
+ CompareOp type;
+};
+using CommandParserCompareOpParsing = testing::TestWithParam<CompareOpData>;
+
+TEST_P(CommandParserCompareOpParsing, Parse) {
+ const auto& test_data = GetParam();
+
+ CommandParser cp;
+ CompareOp op = CompareOp::kNever;
+ Result r = cp.ParseCompareOpNameForTesting(test_data.name, &op);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.type, op);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ CompareOpParsingTests,
+ CommandParserCompareOpParsing,
+ testing::Values(
+ CompareOpData{"VK_COMPARE_OP_NEVER", CompareOp::kNever},
+ CompareOpData{"VK_COMPARE_OP_LESS", CompareOp::kLess},
+ CompareOpData{"VK_COMPARE_OP_EQUAL", CompareOp::kEqual},
+ CompareOpData{"VK_COMPARE_OP_LESS_OR_EQUAL", CompareOp::kLessOrEqual},
+ CompareOpData{"VK_COMPARE_OP_GREATER", CompareOp::kGreater},
+ CompareOpData{"VK_COMPARE_OP_NOT_EQUAL", CompareOp::kNotEqual},
+ CompareOpData{"VK_COMPARE_OP_GREATER_OR_EQUAL",
+ CompareOp::kGreaterOrEqual},
+ CompareOpData{"VK_COMPARE_OP_ALWAYS", CompareOp::kAlways}), );
+
+TEST_F(CommandParserTest, CompareOpParsingInvalid) {
+ CommandParser cp;
+ CompareOp op = CompareOp::kNever;
+ Result r = cp.ParseCompareOpNameForTesting("INVALID", &op);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown CompareOp provided: INVALID", r.Error());
+}
+
+struct CompareOpTest {
+ const char* name;
+};
+using CommandParserCompareOpTests = testing::TestWithParam<CompareOpTest>;
+
+TEST_P(CommandParserCompareOpTests, MissingParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserCompareOpTests, IllegalParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 1.23";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserCompareOpTests, ExtraParam) {
+ const auto& test_data = GetParam();
+
+ std::string data =
+ std::string(test_data.name) + " VK_COMPARE_OP_ALWAYS EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(CompareOpTests,
+ CommandParserCompareOpTests,
+ testing::Values(CompareOpTest{"depthCompareOp"},
+ CompareOpTest{"front.compareOp"},
+ CompareOpTest{"back.compareOp"}), );
+
+TEST_F(CommandParserTest, FrontFailOp) {
+ std::string data = "front.failOp VK_STENCIL_OP_REPLACE";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(StencilOp::kReplace, cp.PipelineDataForTesting()->GetFrontFailOp());
+}
+
+TEST_F(CommandParserTest, FrontPassOp) {
+ std::string data = "front.passOp VK_STENCIL_OP_REPLACE";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(StencilOp::kReplace, cp.PipelineDataForTesting()->GetFrontPassOp());
+}
+
+TEST_F(CommandParserTest, FrontDepthFailOp) {
+ std::string data = "front.depthFailOp VK_STENCIL_OP_REPLACE";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(StencilOp::kReplace,
+ cp.PipelineDataForTesting()->GetFrontDepthFailOp());
+}
+
+TEST_F(CommandParserTest, BackFailOp) {
+ std::string data = "back.failOp VK_STENCIL_OP_REPLACE";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(StencilOp::kReplace, cp.PipelineDataForTesting()->GetBackFailOp());
+}
+
+TEST_F(CommandParserTest, BackPassOp) {
+ std::string data = "back.passOp VK_STENCIL_OP_REPLACE";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(StencilOp::kReplace, cp.PipelineDataForTesting()->GetBackPassOp());
+}
+
+TEST_F(CommandParserTest, BackDepthFailOp) {
+ std::string data = "back.depthFailOp VK_STENCIL_OP_REPLACE";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(StencilOp::kReplace,
+ cp.PipelineDataForTesting()->GetBackDepthFailOp());
+}
+
+struct StencilOpData {
+ const char* name;
+ StencilOp type;
+};
+using CommandParserStencilOpParsing = testing::TestWithParam<StencilOpData>;
+
+TEST_P(CommandParserStencilOpParsing, Parse) {
+ const auto& test_data = GetParam();
+
+ CommandParser cp;
+ StencilOp op = StencilOp::kKeep;
+ Result r = cp.ParseStencilOpNameForTesting(test_data.name, &op);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.type, op);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ CompareOpParsingTests,
+ CommandParserStencilOpParsing,
+ testing::Values(StencilOpData{"VK_STENCIL_OP_KEEP", StencilOp::kKeep},
+ StencilOpData{"VK_STENCIL_OP_ZERO", StencilOp::kZero},
+ StencilOpData{"VK_STENCIL_OP_REPLACE", StencilOp::kReplace},
+ StencilOpData{"VK_STENCIL_OP_INCREMENT_AND_CLAMP",
+ StencilOp::kIncrementAndClamp},
+ StencilOpData{"VK_STENCIL_OP_DECREMENT_AND_CLAMP",
+ StencilOp::kDecrementAndClamp},
+ StencilOpData{"VK_STENCIL_OP_INVERT", StencilOp::kInvert},
+ StencilOpData{"VK_STENCIL_OP_INCREMENT_AND_WRAP",
+ StencilOp::kIncrementAndWrap},
+ StencilOpData{"VK_STENCIL_OP_DECREMENT_AND_WRAP",
+ StencilOp::kDecrementAndWrap}), );
+
+TEST_F(CommandParserTest, StencilOpParsingInvalid) {
+ CommandParser cp;
+ StencilOp op = StencilOp::kKeep;
+ Result r = cp.ParseStencilOpNameForTesting("INVALID", &op);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown StencilOp provided: INVALID", r.Error());
+}
+
+struct StencilOpTest {
+ const char* name;
+};
+using CommandParserStencilOpTests = testing::TestWithParam<StencilOpTest>;
+
+TEST_P(CommandParserStencilOpTests, MissingParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserStencilOpTests, IllegalParam) {
+ const auto& test_data = GetParam();
+
+ std::string data = std::string(test_data.name) + " 1.23";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserStencilOpTests, ExtraParam) {
+ const auto& test_data = GetParam();
+
+ std::string data =
+ std::string(test_data.name) + " VK_STENCIL_OP_REPLACE EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(StencilOpTests,
+ CommandParserStencilOpTests,
+ testing::Values(StencilOpTest{"front.passOp"},
+ StencilOpTest{"front.failOp"},
+ StencilOpTest{"front.depthFailOp"},
+ StencilOpTest{"back.passOp"},
+ StencilOpTest{"back.failOp"},
+ StencilOpTest{"back.depthFailOp"}), );
+
+TEST_F(CommandParserTest, FrontCompareMask) {
+ std::string data = "front.compareMask 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("front.compareMask not implemented", r.Error());
+}
+
+TEST_F(CommandParserTest, FrontWriteMask) {
+ std::string data = "front.writeMask 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("front.writeMask not implemented", r.Error());
+}
+
+TEST_F(CommandParserTest, BackCompareMask) {
+ std::string data = "back.compareMask 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("back.compareMask not implemented", r.Error());
+}
+
+TEST_F(CommandParserTest, BackWriteMask) {
+ std::string data = "back.writeMask 123";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("back.writeMask not implemented", r.Error());
+}
+
+TEST_F(CommandParserTest, FrontReference) {
+ std::string data = "front.reference 10";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(10U, cp.PipelineDataForTesting()->GetFrontReference());
+}
+
+TEST_F(CommandParserTest, BackReference) {
+ std::string data = "back.reference 10";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(10U, cp.PipelineDataForTesting()->GetBackReference());
+}
+
+struct ReferenceData {
+ const char* name;
+};
+
+using CommandParserReferenceTests = testing::TestWithParam<ReferenceData>;
+
+TEST_P(CommandParserReferenceTests, FrontReferenceMissingValue) {
+ const auto& test_data = GetParam();
+ std::string data = std::string(test_data.name);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Missing parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserReferenceTests, FrontReferenceExtraParameters) {
+ const auto& test_data = GetParam();
+ std::string data = std::string(test_data.name) + " 10 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Extra parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+TEST_P(CommandParserReferenceTests, FrontReferenceInvalidParameters) {
+ const auto& test_data = GetParam();
+ std::string data = std::string(test_data.name) + " INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ(std::string("Invalid parameter for ") + test_data.name + " command",
+ r.Error());
+}
+
+INSTANTIATE_TEST_CASE_P(ReferenceTest,
+ CommandParserReferenceTests,
+ testing::Values(ReferenceData{"front.reference"},
+ ReferenceData{"back.reference"}), );
+
+struct ColorMaskData {
+ const char* input;
+ uint8_t result;
+};
+using CommandParserColorMaskTests = testing::TestWithParam<ColorMaskData>;
+
+TEST_P(CommandParserColorMaskTests, ColorWriteMask) {
+ const auto& test_data = GetParam();
+ std::string data = "colorWriteMask " + std::string(test_data.input);
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.result, cp.PipelineDataForTesting()->GetColorWriteMask());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ColorMaskTests,
+ CommandParserColorMaskTests,
+ testing::Values(
+ ColorMaskData{"VK_COLOR_COMPONENT_R_BIT", kColorMaskR},
+ ColorMaskData{"VK_COLOR_COMPONENT_G_BIT", kColorMaskG},
+ ColorMaskData{"VK_COLOR_COMPONENT_B_BIT", kColorMaskB},
+ ColorMaskData{"VK_COLOR_COMPONENT_A_BIT", kColorMaskA},
+ ColorMaskData{"VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | "
+ "VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT",
+ kColorMaskR | kColorMaskG | kColorMaskB | kColorMaskA},
+ ColorMaskData{
+ "VK_COLOR_COMPONENT_A_BIT | VK_COLOR_COMPONENT_B_BIT | "
+ "VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT",
+ kColorMaskR | kColorMaskG | kColorMaskB | kColorMaskA}), );
+
+TEST_F(CommandParserTest, ColorWriteMaskInvalid) {
+ std::string data = "colorWriteMask INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown parameter for colorWriteMask command", r.Error());
+}
+
+TEST_F(CommandParserTest, ColorWriteMaskInvalidAfterValid) {
+ std::string data = "colorWriteMask VK_COLOR_COMPONENT_G_BIT | INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown parameter for colorWriteMask command", r.Error());
+}
+
+TEST_F(CommandParserTest, ColorWriteMaskMissingParam) {
+ std::string data = "colorWriteMask";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing parameter for colorWriteMask command", r.Error());
+}
+
+TEST_F(CommandParserTest, ColorWriteMaskExtraParam) {
+ std::string data =
+ "colorWriteMask VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_B_BIT "
+ "EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Unknown parameter for colorWriteMask command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBO) {
+ std::string data = "ssbo 5 40";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsSSBO());
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(5U, cmd->GetBinding());
+ EXPECT_EQ(40U, cmd->GetSize());
+}
+
+TEST_F(CommandParserTest, SSBOWithDescriptorSet) {
+ std::string data = "ssbo 9:5 40";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsSSBO());
+ EXPECT_EQ(9U, cmd->GetDescriptorSet());
+ EXPECT_EQ(5U, cmd->GetBinding());
+ EXPECT_EQ(40U, cmd->GetSize());
+}
+
+TEST_F(CommandParserTest, SSBOExtraParameter) {
+ std::string data = "ssbo 5 40 EXTRA";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOInvalidFloatBinding) {
+ std::string data = "ssbo 5.0 40";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOInvalidBinding) {
+ std::string data = "ssbo abc 40";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOInvalidFloatSize) {
+ std::string data = "ssbo 5 40.0";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid size value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOInvalidSize) {
+ std::string data = "ssbo 5 abc";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOMissingSize) {
+ std::string data = "ssbo 5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing size value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOMissingBinding) {
+ std::string data = "ssbo";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing binding and size values for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithFloat) {
+ std::string data = "ssbo 6 subdata vec3 2 2.3 4.2 1.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsSSBO());
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ ASSERT_TRUE(cmd->IsSubdata());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.3f, 4.2f, 1.2f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithDescriptorSet) {
+ std::string data = "ssbo 5:6 subdata vec3 2 2.3 4.2 1.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsSSBO());
+ EXPECT_EQ(5U, cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ ASSERT_TRUE(cmd->IsSubdata());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.3f, 4.2f, 1.2f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithInts) {
+ std::string data = "ssbo 6 subdata i16vec3 2 2 4 1";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsSSBO());
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ ASSERT_TRUE(cmd->IsSubdata());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsInt16());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<int16_t> results = {2, 4, 1};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsInt16());
+ }
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithMultipleVectors) {
+ std::string data = "ssbo 6 subdata i16vec3 2 2 4 1 3 6 8";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsSSBO());
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ ASSERT_TRUE(cmd->IsSubdata());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsInt16());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<int16_t> results = {2, 4, 1, 3, 6, 8};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsInt16());
+ }
+}
+
+TEST_F(CommandParserTest, SSBOSubdataMissingBinding) {
+ std::string data = "ssbo subdata i16vec3 2 2 3 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithInvalidBinding) {
+ std::string data = "ssbo INVALID subdata i16vec3 2 2 3 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataMissingSubdataCommand) {
+ std::string data = "ssbo 6 INVALID i16vec3 2 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithBadType) {
+ std::string data = "ssbo 0 subdata INVALID 2 2 3 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid type provided: INVALID", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithInvalidFloatOffset) {
+ std::string data = "ssbo 0 subdata vec2 2.0 3 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithInvalidStringOffset) {
+ std::string data = "ssbo 0 subdata vec2 asdf 3 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset for ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithMissingData) {
+ std::string data = "ssbo 6 subdata i16vec3 2 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Incorrect number of values provided to ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, SSBOSubdataWithMissingAllData) {
+ std::string data = "ssbo 6 subdata i16vec3 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Incorrect number of values provided to ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, Uniform) {
+ std::string data = "uniform vec3 2 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsPushConstant());
+ EXPECT_EQ(2U, cmd->GetOffset());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.1f, 3.2f, 4.3f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, UniformWithContinuation) {
+ std::string data = "uniform vec3 2 2.1 3.2 4.3 \\\n5.4 6.7 8.9";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsPushConstant());
+ EXPECT_EQ(2U, cmd->GetOffset());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.1f, 3.2f, 4.3f, 5.4f, 6.7f, 8.9f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, UniformInvalidType) {
+ std::string data = "uniform INVALID 0 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid type provided: INVALID", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformInvalidFloatOffset) {
+ std::string data = "uniform vec3 5.5 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset value for uniform command", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformInvalidStringOffset) {
+ std::string data = "uniform vec3 INVALID 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset value for uniform command", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformMissingValues) {
+ std::string data = "uniform vec3 2 2.1 3.2 4.3 5.5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Incorrect number of values provided to uniform command",
+ r.Error());
+}
+
+TEST_F(CommandParserTest, UniformUBO) {
+ std::string data = "uniform ubo 2 vec3 1 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsUniform());
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(2U, cmd->GetBinding());
+ EXPECT_EQ(1U, cmd->GetOffset());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.1f, 3.2f, 4.3f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, UniformUBOWithDescriptorSet) {
+ std::string data = "uniform ubo 3:2 vec3 1 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsBuffer());
+
+ auto* cmd = cmds[0]->AsBuffer();
+ EXPECT_TRUE(cmd->IsUniform());
+ EXPECT_EQ(3U, cmd->GetDescriptorSet());
+ EXPECT_EQ(2U, cmd->GetBinding());
+ EXPECT_EQ(1U, cmd->GetOffset());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.1f, 3.2f, 4.3f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, UniformUBOInvalidFloatBinding) {
+ std::string data = "uniform ubo 0.0 vec3 0 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for uniform ubo command", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformUBOInvalidStringBinding) {
+ std::string data = "uniform ubo INVALID vec3 0 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for uniform ubo command", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformUBOInvalidType) {
+ std::string data = "uniform ubo 0 INVALID 0 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid type provided: INVALID", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformUBOInvalidFloatOffset) {
+ std::string data = "uniform ubo 0 vec3 5.5 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset value for uniform command", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformUBOInvalidStringOffset) {
+ std::string data = "uniform ubo 0 vec3 INVALID 2.1 3.2 4.3";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset value for uniform command", r.Error());
+}
+
+TEST_F(CommandParserTest, UniformUBOMissingValues) {
+ std::string data = "uniform ubo 0 vec3 2 2.1 3.2 4.3 5.5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Incorrect number of values provided to uniform command",
+ r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceSingleFloatValue) {
+ std::string data = "tolerance 0.5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ ASSERT_EQ(1U, tolerances.size());
+ EXPECT_FALSE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(0.5, tolerances[0].value);
+}
+
+TEST_F(CommandParserTest, ToleranceSingleFloatPercent) {
+ std::string data = "tolerance 0.5%";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ ASSERT_EQ(1U, tolerances.size());
+ EXPECT_TRUE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(0.5, tolerances[0].value);
+}
+
+TEST_F(CommandParserTest, ToleranceSingleIntValue) {
+ std::string data = "tolerance 5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ ASSERT_EQ(1U, tolerances.size());
+ EXPECT_FALSE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(5.0, tolerances[0].value);
+}
+
+TEST_F(CommandParserTest, ToleranceSingleIntPercent) {
+ std::string data = "tolerance 5%";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ ASSERT_EQ(1U, tolerances.size());
+ EXPECT_TRUE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(5.0, tolerances[0].value);
+}
+
+TEST_F(CommandParserTest, ToleranceMultiFloatValue) {
+ std::string data = "tolerance 0.5 2.4 3.9 99.7";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ std::vector<double> results = {0.5, 2.4, 3.9, 99.7};
+ ASSERT_EQ(results.size(), tolerances.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FALSE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(results[i], tolerances[i].value);
+ }
+}
+
+TEST_F(CommandParserTest, ToleranceMultiFloatValueWithPercent) {
+ std::string data = "tolerance 0.5% 2.4 3.9% 99.7";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ std::vector<double> results = {0.5, 2.4, 3.9, 99.7};
+ ASSERT_EQ(results.size(), tolerances.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ if (i % 2 == 0)
+ EXPECT_TRUE(tolerances[i].is_percent);
+ else
+ EXPECT_FALSE(tolerances[i].is_percent);
+
+ EXPECT_FLOAT_EQ(results[i], tolerances[i].value);
+ }
+}
+
+TEST_F(CommandParserTest, ToleranceMultiIntValue) {
+ std::string data = "tolerance 5 4 3 99";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ std::vector<double> results = {5.0, 4.0, 3.0, 99.0};
+ ASSERT_EQ(results.size(), tolerances.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FALSE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(results[i], tolerances[i].value);
+ }
+}
+
+TEST_F(CommandParserTest, ToleranceMultiIntValueWithPercent) {
+ std::string data = "tolerance 5% 4 3% 99";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ std::vector<double> results = {5.0, 4.0, 3.0, 99.0};
+ ASSERT_EQ(results.size(), tolerances.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ if (i % 2 == 0)
+ EXPECT_TRUE(tolerances[i].is_percent);
+ else
+ EXPECT_FALSE(tolerances[i].is_percent);
+
+ EXPECT_FLOAT_EQ(results[i], tolerances[i].value);
+ }
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidValue1) {
+ std::string data = "tolerance INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidJustPercent) {
+ std::string data = "tolerance %";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidValue2) {
+ std::string data = "tolerance 1 INVALID 3 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidValue3) {
+ std::string data = "tolerance 1 2 INVALID 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidValue4) {
+ std::string data = "tolerance 1 2 3 INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceMissingValues) {
+ std::string data = "tolerance";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Missing value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceTooManyValues) {
+ std::string data = "tolerance 1 2 3 4 5";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Extra parameter for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidWithNumber) {
+ std::string data = "tolerance 1INVALID";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for tolerance command", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceInvalidWithMissingValue) {
+ std::string data = "tolerance 1, , 3, 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid number of tolerance parameters provided", r.Error());
+}
+
+TEST_F(CommandParserTest, ToleranceWithCommas) {
+ std::string data = "tolerance 1,2, 3 ,4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsTolerance());
+
+ auto* cmd = cmds[0]->AsTolerance();
+ const auto& tolerances = cmd->GetTolerances();
+
+ std::vector<double> results = {1.0, 2.0, 3.0, 4.0};
+ ASSERT_EQ(results.size(), tolerances.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FALSE(tolerances[0].is_percent);
+ EXPECT_FLOAT_EQ(results[i], tolerances[i].value);
+ }
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithDescriptorSet) {
+ std::string data = "probe ssbo vec3 3:6 2 >= 2.3 4.2 1.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbeSSBO());
+
+ auto* cmd = cmds[0]->AsProbeSSBO();
+ EXPECT_EQ(3U, cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kGreaterOrEqual,
+ cmd->GetComparator());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.3f, 4.2f, 1.2f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithFloats) {
+ std::string data = "probe ssbo vec3 6 2 >= 2.3 4.2 1.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbeSSBO());
+
+ auto* cmd = cmds[0]->AsProbeSSBO();
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kGreaterOrEqual,
+ cmd->GetComparator());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.3f, 4.2f, 1.2f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, MultiProbeSSBOWithFloats) {
+ std::string data =
+ "probe ssbo vec3 6 2 >= 2.3 4.2 1.2\nprobe ssbo vec3 6 2 >= 2.3 4.2 1.2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(2U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbeSSBO());
+
+ auto* cmd = cmds[0]->AsProbeSSBO();
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kGreaterOrEqual,
+ cmd->GetComparator());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsFloat());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<float> results = {2.3f, 4.2f, 1.2f};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsFloat());
+ }
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithInts) {
+ std::string data = "probe ssbo i16vec3 6 2 <= 2 4 1";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbeSSBO());
+
+ auto* cmd = cmds[0]->AsProbeSSBO();
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kLessOrEqual, cmd->GetComparator());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsInt16());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<int16_t> results = {2, 4, 1};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsInt16());
+ }
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithMultipleVectors) {
+ std::string data = "probe ssbo i16vec3 6 2 == 2 4 1 3 6 8";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& cmds = cp.Commands();
+ ASSERT_EQ(1U, cmds.size());
+ ASSERT_TRUE(cmds[0]->IsProbeSSBO());
+
+ auto* cmd = cmds[0]->AsProbeSSBO();
+ EXPECT_EQ(static_cast<uint32_t>(0), cmd->GetDescriptorSet());
+ EXPECT_EQ(6U, cmd->GetBinding());
+ EXPECT_EQ(2U, cmd->GetOffset());
+ EXPECT_EQ(ProbeSSBOCommand::Comparator::kEqual, cmd->GetComparator());
+
+ const auto& type = cmd->GetDatumType();
+ EXPECT_TRUE(type.IsInt16());
+ EXPECT_EQ(1U, type.ColumnCount());
+ EXPECT_EQ(3U, type.RowCount());
+
+ const auto& values = cmd->GetValues();
+ std::vector<int16_t> results = {2, 4, 1, 3, 6, 8};
+ ASSERT_EQ(results.size(), values.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_FLOAT_EQ(results[i], values[i].AsInt16());
+ }
+}
+
+TEST_F(CommandParserTest, ProbeSSBOMissingBinding) {
+ std::string data = "probe ssbo i16vec3 2 == 2 3 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for probe ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithInvalidBinding) {
+ std::string data = "probe ssbo i16vec3 INVALID 2 == 2 3 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid binding value for probe ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithBadType) {
+ std::string data = "probe ssbo INVALID 0 2 == 2 3 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid type provided: INVALID", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithInvalidFloatOffset) {
+ std::string data = "probe ssbo vec2 0 2.0 == 3 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid offset for probe ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithInvalidStringOffset) {
+ std::string data = "probe ssbo vec2 0 INVALID == 3 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value for probe ssbo command", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithInvalidComparator) {
+ std::string data = "probe ssbo vec2 6 2 INVALID 3 2 4";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid comparator", r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithMissingData) {
+ std::string data = "probe ssbo i16vec3 6 2 == 2";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Incorrect number of values provided to probe ssbo command",
+ r.Error());
+}
+
+TEST_F(CommandParserTest, ProbeSSBOWithMissingAllData) {
+ std::string data = "probe ssbo i16vec3 6 2 ==";
+
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Incorrect number of values provided to probe ssbo command",
+ r.Error());
+}
+
+struct ComparatorTest {
+ const char* name;
+ ProbeSSBOCommand::Comparator op;
+};
+using CommandParserComparatorTests = testing::TestWithParam<ComparatorTest>;
+
+TEST_P(CommandParserComparatorTests, Comparator) {
+ const auto& test_data = GetParam();
+
+ CommandParser cp;
+ ProbeSSBOCommand::Comparator result;
+ Result r = cp.ParseComparatorForTesting(test_data.name, &result);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.op, result);
+}
+
+INSTANTIATE_TEST_CASE_P(
+ ComparatorTests,
+ CommandParserComparatorTests,
+ testing::Values(
+ ComparatorTest{"==", ProbeSSBOCommand::Comparator::kEqual},
+ ComparatorTest{"!=", ProbeSSBOCommand::Comparator::kNotEqual},
+ ComparatorTest{"~=", ProbeSSBOCommand::Comparator::kFuzzyEqual},
+ ComparatorTest{"<", ProbeSSBOCommand::Comparator::kLess},
+ ComparatorTest{"<=", ProbeSSBOCommand::Comparator::kLessOrEqual},
+ ComparatorTest{">", ProbeSSBOCommand::Comparator::kGreater},
+ ComparatorTest{">=", ProbeSSBOCommand::Comparator::kGreaterOrEqual}), );
+
+TEST_F(CommandParserTest, ComparatorInvalid) {
+ CommandParser cp;
+ ProbeSSBOCommand::Comparator result;
+ Result r = cp.ParseComparatorForTesting("INVALID", &result);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid comparator", r.Error());
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/datum_type_parser.cc b/src/vkscript/datum_type_parser.cc
new file mode 100644
index 0000000..ca0c6e5
--- /dev/null
+++ b/src/vkscript/datum_type_parser.cc
@@ -0,0 +1,240 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/datum_type_parser.h"
+
+namespace amber {
+namespace vkscript {
+
+DatumTypeParser::DatumTypeParser() = default;
+
+DatumTypeParser::~DatumTypeParser() = default;
+
+Result DatumTypeParser::Parse(const std::string& data) {
+ // TODO(dsinclair): Might want to make this nicer in the future, but this
+ // works and is easy for now.
+ if (data == "int") {
+ type_.SetType(DataType::kInt32);
+ } else if (data == "uint") {
+ type_.SetType(DataType::kUint32);
+ } else if (data == "int8_t") {
+ type_.SetType(DataType::kInt8);
+ } else if (data == "uint8_t") {
+ type_.SetType(DataType::kUint8);
+ } else if (data == "int16_t") {
+ type_.SetType(DataType::kInt16);
+ } else if (data == "uint16_t") {
+ type_.SetType(DataType::kUint16);
+ } else if (data == "int64_t") {
+ type_.SetType(DataType::kInt64);
+ } else if (data == "uint64_t") {
+ type_.SetType(DataType::kUint64);
+ } else if (data == "float") {
+ type_.SetType(DataType::kFloat);
+ } else if (data == "double") {
+ type_.SetType(DataType::kDouble);
+ } else if (data == "vec2") {
+ type_.SetType(DataType::kFloat);
+ type_.SetRowCount(2);
+ } else if (data == "vec3") {
+ type_.SetType(DataType::kFloat);
+ type_.SetRowCount(3);
+ } else if (data == "vec4") {
+ type_.SetType(DataType::kFloat);
+ type_.SetRowCount(4);
+ } else if (data == "dvec2") {
+ type_.SetType(DataType::kDouble);
+ type_.SetRowCount(2);
+ } else if (data == "dvec3") {
+ type_.SetType(DataType::kDouble);
+ type_.SetRowCount(3);
+ } else if (data == "dvec4") {
+ type_.SetType(DataType::kDouble);
+ type_.SetRowCount(4);
+ } else if (data == "ivec2") {
+ type_.SetType(DataType::kInt32);
+ type_.SetRowCount(2);
+ } else if (data == "ivec3") {
+ type_.SetType(DataType::kInt32);
+ type_.SetRowCount(3);
+ } else if (data == "ivec4") {
+ type_.SetType(DataType::kInt32);
+ type_.SetRowCount(4);
+ } else if (data == "uvec2") {
+ type_.SetType(DataType::kUint32);
+ type_.SetRowCount(2);
+ } else if (data == "uvec3") {
+ type_.SetType(DataType::kUint32);
+ type_.SetRowCount(3);
+ } else if (data == "uvec4") {
+ type_.SetType(DataType::kUint32);
+ type_.SetRowCount(4);
+ } else if (data == "i8vec2") {
+ type_.SetType(DataType::kInt8);
+ type_.SetRowCount(2);
+ } else if (data == "i8vec3") {
+ type_.SetType(DataType::kInt8);
+ type_.SetRowCount(3);
+ } else if (data == "i8vec4") {
+ type_.SetType(DataType::kInt8);
+ type_.SetRowCount(4);
+ } else if (data == "u8vec2") {
+ type_.SetType(DataType::kUint8);
+ type_.SetRowCount(2);
+ } else if (data == "u8vec3") {
+ type_.SetType(DataType::kUint8);
+ type_.SetRowCount(3);
+ } else if (data == "u8vec4") {
+ type_.SetType(DataType::kUint8);
+ type_.SetRowCount(4);
+ } else if (data == "i16vec2") {
+ type_.SetType(DataType::kInt16);
+ type_.SetRowCount(2);
+ } else if (data == "i16vec3") {
+ type_.SetType(DataType::kInt16);
+ type_.SetRowCount(3);
+ } else if (data == "i16vec4") {
+ type_.SetType(DataType::kInt16);
+ type_.SetRowCount(4);
+ } else if (data == "u16vec2") {
+ type_.SetType(DataType::kUint16);
+ type_.SetRowCount(2);
+ } else if (data == "u16vec3") {
+ type_.SetType(DataType::kUint16);
+ type_.SetRowCount(3);
+ } else if (data == "u16vec4") {
+ type_.SetType(DataType::kUint16);
+ type_.SetRowCount(4);
+ } else if (data == "i64vec2") {
+ type_.SetType(DataType::kInt64);
+ type_.SetRowCount(2);
+ } else if (data == "i64vec3") {
+ type_.SetType(DataType::kInt64);
+ type_.SetRowCount(3);
+ } else if (data == "i64vec4") {
+ type_.SetType(DataType::kInt64);
+ type_.SetRowCount(4);
+ } else if (data == "u64vec2") {
+ type_.SetType(DataType::kUint64);
+ type_.SetRowCount(2);
+ } else if (data == "u64vec3") {
+ type_.SetType(DataType::kUint64);
+ type_.SetRowCount(3);
+ } else if (data == "u64vec4") {
+ type_.SetType(DataType::kUint64);
+ type_.SetRowCount(4);
+ } else if (data == "mat2") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(2);
+ } else if (data == "mat2x2") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(2);
+ } else if (data == "mat2x3") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(3);
+ } else if (data == "mat2x4") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(4);
+ } else if (data == "mat3") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(3);
+ } else if (data == "mat3x2") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(2);
+ } else if (data == "mat3x3") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(3);
+ } else if (data == "mat3x4") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(4);
+ } else if (data == "mat4") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(4);
+ } else if (data == "mat4x2") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(2);
+ } else if (data == "mat4x3") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(3);
+ } else if (data == "mat4x4") {
+ type_.SetType(DataType::kFloat);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(4);
+ } else if (data == "dmat2") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(2);
+ } else if (data == "dmat2x2") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(2);
+ } else if (data == "dmat2x3") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(3);
+ } else if (data == "dmat2x4") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(2);
+ type_.SetRowCount(4);
+ } else if (data == "dmat3") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(3);
+ } else if (data == "dmat3x2") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(2);
+ } else if (data == "dmat3x3") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(3);
+ } else if (data == "dmat3x4") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(3);
+ type_.SetRowCount(4);
+ } else if (data == "dmat4") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(4);
+ } else if (data == "dmat4x2") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(2);
+ } else if (data == "dmat4x3") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(3);
+ } else if (data == "dmat4x4") {
+ type_.SetType(DataType::kDouble);
+ type_.SetColumnCount(4);
+ type_.SetRowCount(4);
+ } else {
+ return Result("Invalid type provided: " + data);
+ }
+ return {};
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/datum_type_parser.h b/src/vkscript/datum_type_parser.h
new file mode 100644
index 0000000..4689e33
--- /dev/null
+++ b/src/vkscript/datum_type_parser.h
@@ -0,0 +1,39 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_DATUM_TYPE_PARSER_H_
+#define SRC_VKSCRIPT_DATUM_TYPE_PARSER_H_
+
+#include "amber/result.h"
+#include "src/datum_type.h"
+
+namespace amber {
+namespace vkscript {
+
+class DatumTypeParser {
+ public:
+ DatumTypeParser();
+ ~DatumTypeParser();
+
+ Result Parse(const std::string& data);
+ const DatumType& GetType() const { return type_; }
+
+ private:
+ DatumType type_;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_DATUM_TYPE_PARSER_H_
diff --git a/src/vkscript/datum_type_parser_test.cc b/src/vkscript/datum_type_parser_test.cc
new file mode 100644
index 0000000..27b6f50
--- /dev/null
+++ b/src/vkscript/datum_type_parser_test.cc
@@ -0,0 +1,130 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/datum_type_parser.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+namespace vkscript {
+
+using DatumTypeParserTest = testing::Test;
+
+TEST_F(DatumTypeParserTest, EmptyType) {
+ DatumTypeParser tp;
+ Result r = tp.Parse("");
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid type provided: ", r.Error());
+}
+
+TEST_F(DatumTypeParserTest, InvalidType) {
+ DatumTypeParser tp;
+ Result r = tp.Parse("INVALID");
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid type provided: INVALID", r.Error());
+}
+
+struct DatumTypeData {
+ const char* name;
+ DataType type;
+ uint32_t column_count;
+ uint32_t row_count;
+};
+using DatumTypeDataTest = testing::TestWithParam<DatumTypeData>;
+
+TEST_P(DatumTypeDataTest, Parser) {
+ const auto& test_data = GetParam();
+
+ DatumTypeParser tp;
+ Result r = tp.Parse(test_data.name);
+ const auto& t = tp.GetType();
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(test_data.type, t.GetType());
+ EXPECT_EQ(test_data.column_count, t.ColumnCount());
+ EXPECT_EQ(test_data.row_count, t.RowCount());
+}
+
+INSTANTIATE_TEST_CASE_P(
+ DatumTypeParserTest1,
+ DatumTypeDataTest,
+ testing::Values(DatumTypeData{"int", DataType::kInt32, 1, 1},
+ DatumTypeData{"uint", DataType::kUint32, 1, 1},
+ DatumTypeData{"int8_t", DataType::kInt8, 1, 1},
+ DatumTypeData{"uint8_t", DataType::kUint8, 1, 1},
+ DatumTypeData{"int16_t", DataType::kInt16, 1, 1},
+ DatumTypeData{"uint16_t", DataType::kUint16, 1, 1},
+ DatumTypeData{"int64_t", DataType::kInt64, 1, 1},
+ DatumTypeData{"uint64_t", DataType::kUint64, 1, 1},
+ DatumTypeData{"float", DataType::kFloat, 1, 1},
+ DatumTypeData{"double", DataType::kDouble, 1, 1},
+ DatumTypeData{"vec2", DataType::kFloat, 1, 2},
+ DatumTypeData{"vec3", DataType::kFloat, 1, 3},
+ DatumTypeData{"vec4", DataType::kFloat, 1, 4},
+ DatumTypeData{"dvec2", DataType::kDouble, 1, 2},
+ DatumTypeData{"dvec3", DataType::kDouble, 1, 3},
+ DatumTypeData{"dvec4", DataType::kDouble, 1, 4},
+ DatumTypeData{"ivec2", DataType::kInt32, 1, 2},
+ DatumTypeData{"ivec3", DataType::kInt32, 1, 3},
+ DatumTypeData{"ivec4", DataType::kInt32, 1, 4},
+ DatumTypeData{"uvec2", DataType::kUint32, 1, 2},
+ DatumTypeData{"uvec3", DataType::kUint32, 1, 3},
+ DatumTypeData{"uvec4", DataType::kUint32, 1, 4},
+ DatumTypeData{"i8vec2", DataType::kInt8, 1, 2},
+ DatumTypeData{"i8vec3", DataType::kInt8, 1, 3},
+ DatumTypeData{"i8vec4", DataType::kInt8, 1, 4},
+ DatumTypeData{"u8vec2", DataType::kUint8, 1, 2},
+ DatumTypeData{"u8vec3", DataType::kUint8, 1, 3},
+ DatumTypeData{"u8vec4", DataType::kUint8, 1, 4},
+ DatumTypeData{"i16vec2", DataType::kInt16, 1, 2}), );
+
+INSTANTIATE_TEST_CASE_P(
+ DatumTypeParserTest2,
+ DatumTypeDataTest,
+ testing::Values(DatumTypeData{"i16vec3", DataType::kInt16, 1, 3},
+ DatumTypeData{"i16vec4", DataType::kInt16, 1, 4},
+ DatumTypeData{"u16vec2", DataType::kUint16, 1, 2},
+ DatumTypeData{"u16vec3", DataType::kUint16, 1, 3},
+ DatumTypeData{"u16vec4", DataType::kUint16, 1, 4},
+ DatumTypeData{"i64vec2", DataType::kInt64, 1, 2},
+ DatumTypeData{"i64vec3", DataType::kInt64, 1, 3},
+ DatumTypeData{"i64vec4", DataType::kInt64, 1, 4},
+ DatumTypeData{"u64vec2", DataType::kUint64, 1, 2},
+ DatumTypeData{"u64vec3", DataType::kUint64, 1, 3},
+ DatumTypeData{"u64vec4", DataType::kUint64, 1, 4},
+ DatumTypeData{"mat2", DataType::kFloat, 2, 2},
+ DatumTypeData{"mat2x2", DataType::kFloat, 2, 2},
+ DatumTypeData{"mat2x3", DataType::kFloat, 2, 3},
+ DatumTypeData{"mat2x4", DataType::kFloat, 2, 4},
+ DatumTypeData{"mat3", DataType::kFloat, 3, 3},
+ DatumTypeData{"mat3x2", DataType::kFloat, 3, 2},
+ DatumTypeData{"mat3x3", DataType::kFloat, 3, 3},
+ DatumTypeData{"mat3x4", DataType::kFloat, 3, 4},
+ DatumTypeData{"mat4", DataType::kFloat, 4, 4},
+ DatumTypeData{"mat4x2", DataType::kFloat, 4, 2},
+ DatumTypeData{"mat4x3", DataType::kFloat, 4, 3},
+ DatumTypeData{"mat4x4", DataType::kFloat, 4, 4},
+ DatumTypeData{"dmat2", DataType::kDouble, 2, 2},
+ DatumTypeData{"dmat2x2", DataType::kDouble, 2, 2},
+ DatumTypeData{"dmat2x3", DataType::kDouble, 2, 3},
+ DatumTypeData{"dmat2x4", DataType::kDouble, 2, 4},
+ DatumTypeData{"dmat3", DataType::kDouble, 3, 3},
+ DatumTypeData{"dmat3x2", DataType::kDouble, 3, 2},
+ DatumTypeData{"dmat3x3", DataType::kDouble, 3, 3},
+ DatumTypeData{"dmat3x4", DataType::kDouble, 3, 4},
+ DatumTypeData{"dmat4", DataType::kDouble, 4, 4},
+ DatumTypeData{"dmat4x2", DataType::kDouble, 4, 2},
+ DatumTypeData{"dmat4x3", DataType::kDouble, 4, 3},
+ DatumTypeData{"dmat4x4", DataType::kDouble, 4, 4}), );
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/executor.cc b/src/vkscript/executor.cc
new file mode 100644
index 0000000..bd6bad0
--- /dev/null
+++ b/src/vkscript/executor.cc
@@ -0,0 +1,154 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/executor.h"
+
+#include <cassert>
+#include <vector>
+
+#include "src/engine.h"
+#include "src/vkscript/nodes.h"
+#include "src/vkscript/script.h"
+
+namespace amber {
+namespace vkscript {
+
+Executor::Executor() : amber::Executor() {}
+
+Executor::~Executor() = default;
+
+Result Executor::Execute(Engine* engine, const amber::Script* src_script) {
+ if (!src_script->IsVkScript())
+ return Result("VkScript Executor called with non-vkscript source");
+
+ const Script* script = ToVkScript(src_script);
+
+ // Process Requirement nodes
+ for (const auto& node : script->Nodes()) {
+ if (!node->IsRequire())
+ continue;
+
+ for (const auto& require : node->AsRequire()->Requirements()) {
+ Result r =
+ engine->AddRequirement(require.GetFeature(), require.GetFormat());
+ if (!r.IsSuccess())
+ return r;
+ }
+ }
+
+ // Process Shader nodes
+ PipelineType pipeline_type = PipelineType::kGraphics;
+ for (const auto& node : script->Nodes()) {
+ if (!node->IsShader())
+ continue;
+
+ const auto shader = node->AsShader();
+ Result r = engine->SetShader(shader->GetShaderType(), shader->GetData());
+ if (!r.IsSuccess())
+ return r;
+
+ if (shader->GetShaderType() == ShaderType::kCompute)
+ pipeline_type = PipelineType::kCompute;
+ }
+
+ // TODO(jaebaek): Support multiple pipelines.
+ Result r = engine->CreatePipeline(pipeline_type);
+ if (!r.IsSuccess())
+ return r;
+
+ // Process VertexData nodes
+ for (const auto& node : script->Nodes()) {
+ if (!node->IsVertexData())
+ continue;
+
+ const auto data = node->AsVertexData();
+ const auto& headers = data->GetHeaders();
+ const auto& rows = data->GetRows();
+ for (size_t i = 0; i < headers.size(); ++i) {
+ std::vector<Value> values;
+ for (const auto& row : rows) {
+ const auto& cell = row[i];
+ for (size_t z = 0; z < cell.size(); ++z)
+ values.push_back(cell.GetValue(z));
+ }
+
+ r = engine->SetBuffer(BufferType::kVertexData, headers[i].location,
+ *(headers[i].format), values);
+ if (!r.IsSuccess())
+ return r;
+ }
+ }
+
+ // Process Indices nodes
+ for (const auto& node : script->Nodes()) {
+ if (!node->IsIndices())
+ continue;
+
+ std::vector<Value> values;
+ for (uint16_t index : node->AsIndices()->Indices()) {
+ values.push_back(Value());
+ values.back().SetIntValue(index);
+ }
+
+ r = engine->SetBuffer(BufferType::kIndices, 0, Format(), values);
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ // Process Test nodes
+ for (const auto& node : script->Nodes()) {
+ if (!node->IsTest())
+ continue;
+
+ for (const auto& cmd : node->AsTest()->GetCommands()) {
+ if (cmd->IsClear()) {
+ r = engine->ExecuteClear(cmd->AsClear());
+ } else if (cmd->IsClearColor()) {
+ r = engine->ExecuteClearColor(cmd->AsClearColor());
+ } else if (cmd->IsClearDepth()) {
+ r = engine->ExecuteClearDepth(cmd->AsClearDepth());
+ } else if (cmd->IsClearStencil()) {
+ r = engine->ExecuteClearStencil(cmd->AsClearStencil());
+ } else if (cmd->IsDrawRect()) {
+ r = engine->ExecuteDrawRect(cmd->AsDrawRect());
+ } else if (cmd->IsDrawArrays()) {
+ r = engine->ExecuteDrawArrays(cmd->AsDrawArrays());
+ } else if (cmd->IsCompute()) {
+ r = engine->ExecuteCompute(cmd->AsCompute());
+ } else if (cmd->IsEntryPoint()) {
+ r = engine->ExecuteEntryPoint(cmd->AsEntryPoint());
+ } else if (cmd->IsPatchParameterVertices()) {
+ r = engine->ExecutePatchParameterVertices(
+ cmd->AsPatchParameterVertices());
+ } else if (cmd->IsProbe()) {
+ r = engine->ExecuteProbe(cmd->AsProbe());
+ } else if (cmd->IsProbeSSBO()) {
+ r = engine->ExecuteProbeSSBO(cmd->AsProbeSSBO());
+ } else if (cmd->IsBuffer()) {
+ r = engine->ExecuteBuffer(cmd->AsBuffer());
+ } else if (cmd->IsTolerance()) {
+ r = engine->ExecuteTolerance(cmd->AsTolerance());
+ } else {
+ return Result("Unknown command type");
+ }
+
+ if (!r.IsSuccess())
+ return r;
+ }
+ }
+ return {};
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/executor.h b/src/vkscript/executor.h
new file mode 100644
index 0000000..005ece5
--- /dev/null
+++ b/src/vkscript/executor.h
@@ -0,0 +1,35 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_EXECUTOR_H_
+#define SRC_VKSCRIPT_EXECUTOR_H_
+
+#include "amber/result.h"
+#include "src/executor.h"
+
+namespace amber {
+namespace vkscript {
+
+class Executor : public amber::Executor {
+ public:
+ Executor();
+ ~Executor() override;
+
+ Result Execute(Engine* engine, const amber::Script* script) override;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_EXECUTOR_H_
diff --git a/src/vkscript/executor_test.cc b/src/vkscript/executor_test.cc
new file mode 100644
index 0000000..53af91e
--- /dev/null
+++ b/src/vkscript/executor_test.cc
@@ -0,0 +1,988 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/executor.h"
+#include "gtest/gtest.h"
+#include "src/engine.h"
+#include "src/make_unique.h"
+#include "src/vkscript/parser.h"
+
+namespace amber {
+namespace vkscript {
+namespace {
+
+class EngineStub : public Engine {
+ public:
+ struct Require {
+ Require(Feature feat, Format* fmt) : feature(feat), format(fmt) {}
+
+ Feature feature;
+ Format* format;
+ };
+
+ EngineStub() : Engine() {}
+ ~EngineStub() override = default;
+
+ // Engine
+ Result Initialize() override { return {}; }
+
+ Result InitializeWithDevice(void*) override { return {}; }
+
+ Result Shutdown() override { return {}; }
+
+ void FailRequirements() { fail_requirements_ = true; }
+ const std::vector<Require>& GetRequirements() const { return requirements_; }
+ Result AddRequirement(Feature feature, const Format* format) override {
+ if (fail_requirements_)
+ return Result("requirements failed");
+
+ requirements_.emplace_back(feature, const_cast<Format*>(format));
+ return {};
+ }
+ Result CreatePipeline(PipelineType) override { return {}; }
+
+ void FailShaderCommand() { fail_shader_command_ = true; }
+ const std::vector<ShaderType>& GetShaderTypesSeen() const {
+ return shaders_seen_;
+ }
+ Result SetShader(ShaderType type, const std::vector<uint32_t>&) override {
+ if (fail_shader_command_)
+ return Result("shader command failed");
+
+ shaders_seen_.push_back(type);
+ return {};
+ }
+
+ uint8_t GetBufferCallCount() const { return buffer_call_count_; }
+ BufferType GetBufferType(size_t idx) const { return buffer_types_[idx]; }
+ uint8_t GetBufferLocation(size_t idx) const { return buffer_locations_[idx]; }
+ Format* GetBufferFormat(size_t idx) { return &(buffer_formats_[idx]); }
+ const std::vector<Value>& GetBufferValues(size_t idx) const {
+ return buffer_values_[idx];
+ }
+ Result SetBuffer(BufferType type,
+ uint8_t location,
+ const Format& format,
+ const std::vector<Value>& data) override {
+ ++buffer_call_count_;
+ buffer_types_.push_back(type);
+ buffer_locations_.push_back(location);
+ buffer_formats_.push_back(format);
+ buffer_values_.push_back(data);
+ return {};
+ }
+
+ void FailClearColorCommand() { fail_clear_color_command_ = true; }
+ bool DidClearColorCommand() { return did_clear_color_command_ = true; }
+ ClearColorCommand* GetLastClearColorCommand() { return last_clear_color_; }
+ Result ExecuteClearColor(const ClearColorCommand* cmd) override {
+ did_clear_color_command_ = true;
+
+ if (fail_clear_color_command_)
+ return Result("clear color command failed");
+
+ last_clear_color_ = const_cast<ClearColorCommand*>(cmd);
+ return {};
+ }
+
+ void FailClearStencilCommand() { fail_clear_stencil_command_ = true; }
+ bool DidClearStencilCommand() const { return did_clear_stencil_command_; }
+ Result ExecuteClearStencil(const ClearStencilCommand*) override {
+ did_clear_stencil_command_ = true;
+
+ if (fail_clear_stencil_command_)
+ return Result("clear stencil command failed");
+
+ return {};
+ }
+
+ void FailClearDepthCommand() { fail_clear_depth_command_ = true; }
+ bool DidClearDepthCommand() const { return did_clear_depth_command_; }
+ Result ExecuteClearDepth(const ClearDepthCommand*) override {
+ did_clear_depth_command_ = true;
+
+ if (fail_clear_depth_command_)
+ return Result("clear depth command failed");
+
+ return {};
+ }
+
+ void FailClearCommand() { fail_clear_command_ = true; }
+ bool DidClearCommand() const { return did_clear_command_; }
+ Result ExecuteClear(const ClearCommand*) override {
+ did_clear_command_ = true;
+
+ if (fail_clear_command_)
+ return Result("clear command failed");
+ return {};
+ }
+
+ void FailDrawRectCommand() { fail_draw_rect_command_ = true; }
+ bool DidDrawRectCommand() const { return did_draw_rect_command_; }
+ Result ExecuteDrawRect(const DrawRectCommand*) override {
+ did_draw_rect_command_ = true;
+
+ if (fail_draw_rect_command_)
+ return Result("draw rect command failed");
+ return {};
+ }
+
+ void FailDrawArraysCommand() { fail_draw_arrays_command_ = true; }
+ bool DidDrawArraysCommand() const { return did_draw_arrays_command_; }
+ Result ExecuteDrawArrays(const DrawArraysCommand*) override {
+ did_draw_arrays_command_ = true;
+
+ if (fail_draw_arrays_command_)
+ return Result("draw arrays command failed");
+ return {};
+ }
+
+ void FailComputeCommand() { fail_compute_command_ = true; }
+ bool DidComputeCommand() const { return did_compute_command_; }
+ Result ExecuteCompute(const ComputeCommand*) override {
+ did_compute_command_ = true;
+
+ if (fail_compute_command_)
+ return Result("compute command failed");
+ return {};
+ }
+
+ void FailEntryPointCommand() { fail_entry_point_command_ = true; }
+ bool DidEntryPointCommand() const { return did_entry_point_command_; }
+ Result ExecuteEntryPoint(const EntryPointCommand*) override {
+ did_entry_point_command_ = true;
+
+ if (fail_entry_point_command_)
+ return Result("entrypoint command failed");
+ return {};
+ }
+
+ void FailPatchParameterVerticesCommand() { fail_patch_command_ = true; }
+ bool DidPatchParameterVerticesCommand() const { return did_patch_command_; }
+ Result ExecutePatchParameterVertices(
+ const PatchParameterVerticesCommand*) override {
+ did_patch_command_ = true;
+
+ if (fail_patch_command_)
+ return Result("patch command failed");
+ return {};
+ }
+
+ void FailProbeCommand() { fail_probe_command_ = true; }
+ bool DidProbeCommand() const { return did_probe_commmand_; }
+ Result ExecuteProbe(const ProbeCommand*) override {
+ did_probe_commmand_ = true;
+
+ if (fail_probe_command_)
+ return Result("probe command failed");
+ return {};
+ }
+
+ void FailProbeSSBOCommand() { fail_probe_ssbo_command_ = true; }
+ bool DidProbeSSBOCommand() const { return did_probe_ssbo_command_; }
+ Result ExecuteProbeSSBO(const ProbeSSBOCommand*) override {
+ did_probe_ssbo_command_ = true;
+
+ if (fail_probe_ssbo_command_)
+ return Result("probe ssbo command failed");
+ return {};
+ }
+
+ void FailBufferCommand() { fail_buffer_command_ = true; }
+ bool DidBufferCommand() const { return did_buffer_command_; }
+ Result ExecuteBuffer(const BufferCommand*) override {
+ did_buffer_command_ = true;
+
+ if (fail_buffer_command_)
+ return Result("buffer command failed");
+ return {};
+ }
+
+ void FailToleranceCommand() { fail_tolerance_command_ = true; }
+ bool DidToleranceCommand() const { return did_tolerance_command_; }
+ Result ExecuteTolerance(const ToleranceCommand*) override {
+ did_tolerance_command_ = true;
+
+ if (fail_tolerance_command_)
+ return Result("tolerance command failed");
+ return {};
+ }
+
+ private:
+ bool fail_requirements_ = false;
+ bool fail_shader_command_ = false;
+ bool fail_clear_command_ = false;
+ bool fail_clear_color_command_ = false;
+ bool fail_clear_stencil_command_ = false;
+ bool fail_clear_depth_command_ = false;
+ bool fail_draw_rect_command_ = false;
+ bool fail_draw_arrays_command_ = false;
+ bool fail_compute_command_ = false;
+ bool fail_entry_point_command_ = false;
+ bool fail_patch_command_ = false;
+ bool fail_probe_command_ = false;
+ bool fail_probe_ssbo_command_ = false;
+ bool fail_buffer_command_ = false;
+ bool fail_tolerance_command_ = false;
+
+ bool did_clear_command_ = false;
+ bool did_clear_color_command_ = false;
+ bool did_clear_stencil_command_ = false;
+ bool did_clear_depth_command_ = false;
+ bool did_draw_rect_command_ = false;
+ bool did_draw_arrays_command_ = false;
+ bool did_compute_command_ = false;
+ bool did_entry_point_command_ = false;
+ bool did_patch_command_ = false;
+ bool did_probe_commmand_ = false;
+ bool did_probe_ssbo_command_ = false;
+ bool did_buffer_command_ = false;
+ bool did_tolerance_command_ = false;
+
+ uint8_t buffer_call_count_ = 0;
+ std::vector<uint8_t> buffer_locations_;
+ std::vector<BufferType> buffer_types_;
+ std::vector<Format> buffer_formats_;
+ std::vector<std::vector<Value>> buffer_values_;
+
+ std::vector<ShaderType> shaders_seen_;
+ std::vector<Require> requirements_;
+
+ ClearColorCommand* last_clear_color_ = nullptr;
+};
+
+class EngineCountingStub : public Engine {
+ public:
+ EngineCountingStub() : Engine() {}
+ ~EngineCountingStub() override = default;
+
+ // Engine
+ Result Initialize() override { return {}; }
+
+ Result InitializeWithDevice(void*) override { return {}; }
+
+ Result Shutdown() override { return {}; }
+
+ int32_t GetRequireStageIdx() const { return require_stage_; }
+ uint32_t GetRequireCount() const { return require_stage_count_; }
+ Result AddRequirement(Feature, const Format*) override {
+ ++require_stage_count_;
+ require_stage_ = stage_count_++;
+ return {};
+ }
+ Result CreatePipeline(PipelineType) override { return {}; }
+
+ int32_t GetShaderStageIdx() const { return shader_stage_; }
+ uint32_t GetShaderCount() const { return shader_stage_count_; }
+ Result SetShader(ShaderType, const std::vector<uint32_t>&) override {
+ ++shader_stage_count_;
+ shader_stage_ = stage_count_++;
+ return {};
+ }
+ Result SetBuffer(BufferType,
+ uint8_t,
+ const Format&,
+ const std::vector<Value>&) override {
+ return {};
+ }
+
+ Result ExecuteClearColor(const ClearColorCommand*) override { return {}; }
+ Result ExecuteClearStencil(const ClearStencilCommand*) override { return {}; }
+ Result ExecuteClearDepth(const ClearDepthCommand*) override { return {}; }
+ Result ExecuteClear(const ClearCommand*) override { return {}; }
+ Result ExecuteDrawRect(const DrawRectCommand*) override { return {}; }
+ Result ExecuteDrawArrays(const DrawArraysCommand*) override { return {}; }
+ Result ExecuteCompute(const ComputeCommand*) override { return {}; }
+ Result ExecuteEntryPoint(const EntryPointCommand*) override { return {}; }
+ Result ExecutePatchParameterVertices(
+ const PatchParameterVerticesCommand*) override {
+ return {};
+ }
+ Result ExecuteProbe(const ProbeCommand*) override { return {}; }
+ Result ExecuteProbeSSBO(const ProbeSSBOCommand*) override { return {}; }
+ Result ExecuteBuffer(const BufferCommand*) override { return {}; }
+ Result ExecuteTolerance(const ToleranceCommand*) override { return {}; }
+
+ private:
+ int32_t stage_count_ = 0;
+ int32_t require_stage_ = -1;
+ int32_t shader_stage_ = -1;
+ uint32_t require_stage_count_ = 0;
+ uint32_t shader_stage_count_ = 0;
+};
+
+class VkScriptExecutorTest : public testing::Test {
+ public:
+ VkScriptExecutorTest() = default;
+ ~VkScriptExecutorTest() = default;
+
+ std::unique_ptr<Engine> MakeEngine() { return MakeUnique<EngineStub>(); }
+ std::unique_ptr<Engine> MakeCountingEngine() {
+ return MakeUnique<EngineCountingStub>();
+ }
+ EngineStub* ToStub(Engine* engine) {
+ return static_cast<EngineStub*>(engine);
+ }
+
+ EngineCountingStub* ToCountingStub(Engine* engine) {
+ return static_cast<EngineCountingStub*>(engine);
+ }
+};
+
+} // namespace
+
+TEST_F(VkScriptExecutorTest, ExecutesRequirements) {
+ std::string input = R"(
+[require]
+robustBufferAccess
+logicOp)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto requirements = ToStub(engine.get())->GetRequirements();
+ ASSERT_EQ(2U, requirements.size());
+ EXPECT_EQ(Feature::kRobustBufferAccess, requirements[0].feature);
+ EXPECT_EQ(Feature::kLogicOp, requirements[1].feature);
+}
+
+TEST_F(VkScriptExecutorTest, ExecutesAllRequirementsFirst) {
+ std::string input = R"(
+[require]
+robustBufferAccess
+logicOp
+[vertex shader passthrough]
+[require]
+framebuffer R32G32B32A32_SINT
+)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeCountingEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto count = ToCountingStub(engine.get());
+ // All requires run first (3 requirements), so they get in before shader
+ EXPECT_EQ(2U, count->GetRequireStageIdx());
+ EXPECT_EQ(3U, count->GetShaderStageIdx());
+ EXPECT_EQ(3U, count->GetRequireCount());
+ EXPECT_EQ(1U, count->GetShaderCount());
+}
+
+TEST_F(VkScriptExecutorTest, ExecuteRequirementsFailed) {
+ std::string input = R"(
+[require]
+robustBufferAccess
+logicOp)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailRequirements();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("requirements failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ExecutesRequirementsWithFormat) {
+ std::string input = R"(
+[require]
+framebuffer R32G32B32A32_SINT)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto requirements = ToStub(engine.get())->GetRequirements();
+ ASSERT_EQ(1U, requirements.size());
+ EXPECT_EQ(Feature::kFramebuffer, requirements[0].feature);
+ EXPECT_EQ(FormatType::kR32G32B32A32_SINT,
+ requirements[0].format->GetFormatType());
+}
+
+TEST_F(VkScriptExecutorTest, ExecutesShaders) {
+ std::string input = R"(
+[vertex shader passthrough]
+[fragment shader]
+#version 430
+void main() {}
+)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto shader_types = ToStub(engine.get())->GetShaderTypesSeen();
+ ASSERT_EQ(2U, shader_types.size());
+ EXPECT_EQ(ShaderType::kVertex, shader_types[0]);
+ EXPECT_EQ(ShaderType::kFragment, shader_types[1]);
+}
+
+TEST_F(VkScriptExecutorTest, ShaderFailure) {
+ std::string input = R"(
+[vertex shader passthrough]
+[fragment shader]
+#version 430
+void main() {}
+)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailShaderCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("shader command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ClearCommand) {
+ std::string input = R"(
+[test]
+clear)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ EXPECT_TRUE(ToStub(engine.get())->DidClearCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ClearCommandFailure) {
+ std::string input = R"(
+[test]
+clear)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailClearCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("clear command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ClearColorCommand) {
+ std::string input = R"(
+[test]
+clear color 244 123 123 13)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidClearColorCommand());
+
+ auto* cmd = ToStub(engine.get())->GetLastClearColorCommand();
+ ASSERT_TRUE(cmd != nullptr);
+ ASSERT_TRUE(cmd->IsClearColor());
+
+ EXPECT_EQ(244U, cmd->GetR());
+ EXPECT_EQ(123U, cmd->GetG());
+ EXPECT_EQ(123U, cmd->GetB());
+ EXPECT_EQ(13U, cmd->GetA());
+}
+
+TEST_F(VkScriptExecutorTest, ClearColorCommandFailure) {
+ std::string input = R"(
+[test]
+clear color 123 123 123 123)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailClearColorCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("clear color command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ClearDepthCommand) {
+ std::string input = R"(
+[test]
+clear depth 24)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidClearDepthCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ClearDepthCommandFailure) {
+ std::string input = R"(
+[test]
+clear depth 24)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailClearDepthCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("clear depth command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ClearStencilCommand) {
+ std::string input = R"(
+[test]
+clear stencil 24)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidClearStencilCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ClearStencilCommandFailure) {
+ std::string input = R"(
+[test]
+clear stencil 24)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailClearStencilCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("clear stencil command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, DrawRectCommand) {
+ std::string input = R"(
+[test]
+draw rect 2 4 10 20)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidDrawRectCommand());
+}
+
+TEST_F(VkScriptExecutorTest, DrawRectCommandFailure) {
+ std::string input = R"(
+[test]
+draw rect 2 4 10 20)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailDrawRectCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("draw rect command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, DrawArraysCommand) {
+ std::string input = R"(
+[test]
+draw arrays TRIANGLE_LIST 0 0)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidDrawArraysCommand());
+}
+
+TEST_F(VkScriptExecutorTest, DrawArraysCommandFailure) {
+ std::string input = R"(
+[test]
+draw arrays TRIANGLE_LIST 0 0)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailDrawArraysCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("draw arrays command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ComputeCommand) {
+ std::string input = R"(
+[test]
+compute 2 3 4)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidComputeCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ComputeCommandFailure) {
+ std::string input = R"(
+[test]
+compute 2 3 4)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailComputeCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("compute command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, EntryPointCommand) {
+ std::string input = R"(
+[test]
+vertex entrypoint main)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidEntryPointCommand());
+}
+
+TEST_F(VkScriptExecutorTest, EntryPointCommandFailure) {
+ std::string input = R"(
+[test]
+vertex entrypoint main)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailEntryPointCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("entrypoint command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, PatchParameterVerticesCommand) {
+ std::string input = R"(
+[test]
+patch parameter vertices 10)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidPatchParameterVerticesCommand());
+}
+
+TEST_F(VkScriptExecutorTest, PatchParameterVerticesCommandFailure) {
+ std::string input = R"(
+[test]
+patch parameter vertices 10)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailPatchParameterVerticesCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("patch command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ProbeCommand) {
+ std::string input = R"(
+[test]
+probe rect rgba 2 3 40 40 0.2 0.4 0.4 0.3)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidProbeCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ProbeCommandFailure) {
+ std::string input = R"(
+[test]
+probe rect rgba 2 3 40 40 0.2 0.4 0.4 0.3)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailProbeCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("probe command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, BufferCommand) {
+ std::string input = R"(
+[test]
+ssbo 0 24)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidBufferCommand());
+}
+
+TEST_F(VkScriptExecutorTest, BufferCommandFailure) {
+ std::string input = R"(
+[test]
+ssbo 0 24)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailBufferCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("buffer command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ToleranceCommand) {
+ std::string input = R"(
+[test]
+tolerance 2 4 5 8)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidToleranceCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ToleranceCommandFailure) {
+ std::string input = R"(
+[test]
+tolerance 2 3 4 5)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailToleranceCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("tolerance command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, ProbeSSBOCommand) {
+ std::string input = R"(
+[test]
+probe ssbo vec3 0 2 <= 2 3 4)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+ ASSERT_TRUE(ToStub(engine.get())->DidProbeSSBOCommand());
+}
+
+TEST_F(VkScriptExecutorTest, ProbeSSBOCommandFailure) {
+ std::string input = R"(
+[test]
+probe ssbo vec3 0 2 <= 2 3 4)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+
+ auto engine = MakeEngine();
+ ToStub(engine.get())->FailProbeSSBOCommand();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("probe ssbo command failed", r.Error());
+}
+
+TEST_F(VkScriptExecutorTest, VertexData) {
+ std::string input = R"(
+[vertex data]
+9/R32G32B32_SFLOAT 1/R8G8B8_UNORM
+-1 -1 0.25 255 128 64
+0.25 -1 0.25 255 0 0
+)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto stub = ToStub(engine.get());
+ ASSERT_EQ(2U, stub->GetBufferCallCount());
+
+ EXPECT_EQ(FormatType::kR32G32B32_SFLOAT,
+ stub->GetBufferFormat(0)->GetFormatType());
+ EXPECT_EQ(BufferType::kVertexData, stub->GetBufferType(0));
+ EXPECT_EQ(9U, stub->GetBufferLocation(0));
+
+ const auto& data1 = stub->GetBufferValues(0);
+ std::vector<float> results1 = {-1, -1, 0.25, 0.25, -1, 0.25};
+ ASSERT_EQ(results1.size(), data1.size());
+ for (size_t i = 0; i < results1.size(); ++i) {
+ ASSERT_TRUE(data1[i].IsFloat());
+ EXPECT_FLOAT_EQ(results1[i], data1[i].AsFloat());
+ }
+
+ EXPECT_EQ(FormatType::kR8G8B8_UNORM,
+ stub->GetBufferFormat(1)->GetFormatType());
+ EXPECT_EQ(BufferType::kVertexData, stub->GetBufferType(1));
+ EXPECT_EQ(1U, stub->GetBufferLocation(1));
+
+ const auto& data2 = stub->GetBufferValues(1);
+ std::vector<uint8_t> results2 = {255, 128, 64, 255, 0, 0};
+ ASSERT_EQ(results2.size(), data2.size());
+ for (size_t i = 0; i < results2.size(); ++i) {
+ ASSERT_TRUE(data2[i].IsInteger());
+ EXPECT_EQ(results2[i], data2[i].AsUint8());
+ }
+}
+
+TEST_F(VkScriptExecutorTest, IndexBuffer) {
+ std::string input = R"(
+[indices]
+1 2 3 4 5 6
+)";
+
+ Parser parser;
+ ASSERT_TRUE(parser.Parse(input).IsSuccess());
+ auto engine = MakeEngine();
+
+ Executor ex;
+ Result r = ex.Execute(engine.get(), parser.GetScript());
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto stub = ToStub(engine.get());
+ ASSERT_EQ(1U, stub->GetBufferCallCount());
+
+ EXPECT_EQ(BufferType::kIndices, stub->GetBufferType(0));
+
+ const auto& data = stub->GetBufferValues(0);
+ std::vector<uint8_t> results = {1, 2, 3, 4, 5, 6};
+ ASSERT_EQ(results.size(), data.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ ASSERT_TRUE(data[i].IsInteger());
+ EXPECT_EQ(results[i], data[i].AsUint8());
+ }
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/format_parser.cc b/src/vkscript/format_parser.cc
new file mode 100644
index 0000000..57da26a
--- /dev/null
+++ b/src/vkscript/format_parser.cc
@@ -0,0 +1,505 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/format_parser.h"
+
+#include <cassert>
+#include <cstdlib>
+
+#include "src/make_unique.h"
+
+namespace amber {
+namespace vkscript {
+
+FormatParser::FormatParser() = default;
+
+FormatParser::~FormatParser() = default;
+
+std::unique_ptr<Format> FormatParser::Parse(const std::string& data) {
+ if (data.empty())
+ return nullptr;
+
+ auto fmt = MakeUnique<Format>();
+
+ // See if this is a custom glsl string format.
+ if (data.find('/', 0) != std::string::npos)
+ return ParseGlslFormat(data);
+
+ FormatType type = NameToType(data);
+ if (type == FormatType::kUnknown)
+ return nullptr;
+
+ fmt->SetFormatType(type);
+
+ size_t cur_pos = 0;
+ while (cur_pos < data.length()) {
+ size_t next_pos = data.find('_', cur_pos);
+ if (next_pos == std::string::npos)
+ next_pos = data.length();
+
+ ProcessChunk(fmt.get(), data.substr(cur_pos, next_pos - cur_pos).c_str());
+ cur_pos = next_pos + 1;
+ }
+
+ assert(pieces_.empty());
+
+ return fmt;
+}
+
+void FormatParser::AddPiece(FormatComponentType type, uint8_t bits) {
+ pieces_.emplace_back(type, bits);
+}
+
+void FormatParser::FlushPieces(Format* fmt, FormatMode mode) {
+ for (const auto& piece : pieces_)
+ fmt->AddComponent(piece.type, mode, piece.num_bits);
+
+ pieces_.clear();
+}
+
+// TODO(dsinclair): Remove asserts return something ...?
+void FormatParser::ProcessChunk(Format* fmt, const std::string& data) {
+ assert(data.size() > 0);
+
+ if (data[0] == 'P') {
+ if (data == "PACK8")
+ fmt->SetPackSize(8);
+ else if (data == "PACK16")
+ fmt->SetPackSize(16);
+ else if (data == "PACK32")
+ fmt->SetPackSize(32);
+ else
+ assert(false);
+
+ return;
+ }
+
+ if (data[0] == 'U') {
+ if (data == "UINT")
+ FlushPieces(fmt, FormatMode::kUInt);
+ else if (data == "UNORM")
+ FlushPieces(fmt, FormatMode::kUNorm);
+ else if (data == "UFLOAT")
+ FlushPieces(fmt, FormatMode::kUFloat);
+ else if (data == "USCALED")
+ FlushPieces(fmt, FormatMode::kUScaled);
+ else
+ assert(false);
+
+ return;
+ }
+
+ if (data[0] == 'S') {
+ if (data == "SINT")
+ FlushPieces(fmt, FormatMode::kSInt);
+ else if (data == "SNORM")
+ FlushPieces(fmt, FormatMode::kSNorm);
+ else if (data == "SSCALED")
+ FlushPieces(fmt, FormatMode::kSScaled);
+ else if (data == "SFLOAT")
+ FlushPieces(fmt, FormatMode::kSFloat);
+ else if (data == "SRGB")
+ FlushPieces(fmt, FormatMode::kSRGB);
+ else if (data == "S8")
+ AddPiece(FormatComponentType::kS, 8);
+ else
+ assert(false);
+
+ return;
+ }
+
+ size_t cur_pos = 0;
+ while (cur_pos < data.size()) {
+ FormatComponentType type = FormatComponentType::kA;
+ switch (data[cur_pos++]) {
+ case 'X':
+ type = FormatComponentType::kX;
+ break;
+ case 'D':
+ type = FormatComponentType::kD;
+ break;
+ case 'R':
+ type = FormatComponentType::kR;
+ break;
+ case 'G':
+ type = FormatComponentType::kG;
+ break;
+ case 'B':
+ type = FormatComponentType::kB;
+ break;
+ case 'A':
+ type = FormatComponentType::kA;
+ break;
+ default:
+ assert(false);
+ }
+
+ assert(cur_pos < data.size());
+
+ char* next_str;
+ const char* str = data.c_str() + cur_pos;
+
+ long val = std::strtol(str, &next_str, 10);
+ AddPiece(type, static_cast<uint8_t>(val));
+
+ cur_pos += static_cast<size_t>(next_str - str);
+ }
+}
+
+FormatType FormatParser::NameToType(const std::string& data) {
+ if (data == "A1R5G5B5_UNORM_PACK16")
+ return FormatType::kA1R5G5B5_UNORM_PACK16;
+ if (data == "A2B10G10R10_SINT_PACK32")
+ return FormatType::kA2B10G10R10_SINT_PACK32;
+ if (data == "A2B10G10R10_SNORM_PACK32")
+ return FormatType::kA2B10G10R10_SNORM_PACK32;
+ if (data == "A2B10G10R10_SSCALED_PACK32")
+ return FormatType::kA2B10G10R10_SSCALED_PACK32;
+ if (data == "A2B10G10R10_UINT_PACK32")
+ return FormatType::kA2B10G10R10_UINT_PACK32;
+ if (data == "A2B10G10R10_UNORM_PACK32")
+ return FormatType::kA2B10G10R10_UNORM_PACK32;
+ if (data == "A2B10G10R10_USCALED_PACK32")
+ return FormatType::kA2B10G10R10_USCALED_PACK32;
+ if (data == "A2R10G10B10_SINT_PACK32")
+ return FormatType::kA2R10G10B10_SINT_PACK32;
+ if (data == "A2R10G10B10_SNORM_PACK32")
+ return FormatType::kA2R10G10B10_SNORM_PACK32;
+ if (data == "A2R10G10B10_SSCALED_PACK32")
+ return FormatType::kA2R10G10B10_SSCALED_PACK32;
+ if (data == "A2R10G10B10_UINT_PACK32")
+ return FormatType::kA2R10G10B10_UINT_PACK32;
+ if (data == "A2R10G10B10_UNORM_PACK32")
+ return FormatType::kA2R10G10B10_UNORM_PACK32;
+ if (data == "A2R10G10B10_USCALED_PACK32")
+ return FormatType::kA2R10G10B10_USCALED_PACK32;
+ if (data == "A8B8G8R8_SINT_PACK32")
+ return FormatType::kA8B8G8R8_SINT_PACK32;
+ if (data == "A8B8G8R8_SNORM_PACK32")
+ return FormatType::kA8B8G8R8_SNORM_PACK32;
+ if (data == "A8B8G8R8_SRGB_PACK32")
+ return FormatType::kA8B8G8R8_SRGB_PACK32;
+ if (data == "A8B8G8R8_SSCALED_PACK32")
+ return FormatType::kA8B8G8R8_SSCALED_PACK32;
+ if (data == "A8B8G8R8_UINT_PACK32")
+ return FormatType::kA8B8G8R8_UINT_PACK32;
+ if (data == "A8B8G8R8_UNORM_PACK32")
+ return FormatType::kA8B8G8R8_UNORM_PACK32;
+ if (data == "A8B8G8R8_USCALED_PACK32")
+ return FormatType::kA8B8G8R8_USCALED_PACK32;
+ if (data == "B10G11R11_UFLOAT_PACK32")
+ return FormatType::kB10G11R11_UFLOAT_PACK32;
+ if (data == "B4G4R4A4_UNORM_PACK16")
+ return FormatType::kB4G4R4A4_UNORM_PACK16;
+ if (data == "B5G5R5A1_UNORM_PACK16")
+ return FormatType::kB5G5R5A1_UNORM_PACK16;
+ if (data == "B5G6R5_UNORM_PACK16")
+ return FormatType::kB5G6R5_UNORM_PACK16;
+ if (data == "B8G8R8A8_SINT")
+ return FormatType::kB8G8R8A8_SINT;
+ if (data == "B8G8R8A8_SNORM")
+ return FormatType::kB8G8R8A8_SNORM;
+ if (data == "B8G8R8A8_SRGB")
+ return FormatType::kB8G8R8A8_SRGB;
+ if (data == "B8G8R8A8_SSCALED")
+ return FormatType::kB8G8R8A8_SSCALED;
+ if (data == "B8G8R8A8_UINT")
+ return FormatType::kB8G8R8A8_UINT;
+ if (data == "B8G8R8A8_UNORM")
+ return FormatType::kB8G8R8A8_UNORM;
+ if (data == "B8G8R8A8_USCALED")
+ return FormatType::kB8G8R8A8_USCALED;
+ if (data == "B8G8R8_SINT")
+ return FormatType::kB8G8R8_SINT;
+ if (data == "B8G8R8_SNORM")
+ return FormatType::kB8G8R8_SNORM;
+ if (data == "B8G8R8_SRGB")
+ return FormatType::kB8G8R8_SRGB;
+ if (data == "B8G8R8_SSCALED")
+ return FormatType::kB8G8R8_SSCALED;
+ if (data == "B8G8R8_UINT")
+ return FormatType::kB8G8R8_UINT;
+ if (data == "B8G8R8_UNORM")
+ return FormatType::kB8G8R8_UNORM;
+ if (data == "B8G8R8_USCALED")
+ return FormatType::kB8G8R8_USCALED;
+ if (data == "D16_UNORM")
+ return FormatType::kD16_UNORM;
+ if (data == "D16_UNORM_S8_UINT")
+ return FormatType::kD16_UNORM_S8_UINT;
+ if (data == "D24_UNORM_S8_UINT")
+ return FormatType::kD24_UNORM_S8_UINT;
+ if (data == "D32_SFLOAT")
+ return FormatType::kD32_SFLOAT;
+ if (data == "D32_SFLOAT_S8_UINT")
+ return FormatType::kD32_SFLOAT_S8_UINT;
+ if (data == "R16G16B16A16_SFLOAT")
+ return FormatType::kR16G16B16A16_SFLOAT;
+ if (data == "R16G16B16A16_SINT")
+ return FormatType::kR16G16B16A16_SINT;
+ if (data == "R16G16B16A16_SNORM")
+ return FormatType::kR16G16B16A16_SNORM;
+ if (data == "R16G16B16A16_SSCALED")
+ return FormatType::kR16G16B16A16_SSCALED;
+ if (data == "R16G16B16A16_UINT")
+ return FormatType::kR16G16B16A16_UINT;
+ if (data == "R16G16B16A16_UNORM")
+ return FormatType::kR16G16B16A16_UNORM;
+ if (data == "R16G16B16A16_USCALED")
+ return FormatType::kR16G16B16A16_USCALED;
+ if (data == "R16G16B16_SFLOAT")
+ return FormatType::kR16G16B16_SFLOAT;
+ if (data == "R16G16B16_SINT")
+ return FormatType::kR16G16B16_SINT;
+ if (data == "R16G16B16_SNORM")
+ return FormatType::kR16G16B16_SNORM;
+ if (data == "R16G16B16_SSCALED")
+ return FormatType::kR16G16B16_SSCALED;
+ if (data == "R16G16B16_UINT")
+ return FormatType::kR16G16B16_UINT;
+ if (data == "R16G16B16_UNORM")
+ return FormatType::kR16G16B16_UNORM;
+ if (data == "R16G16B16_USCALED")
+ return FormatType::kR16G16B16_USCALED;
+ if (data == "R16G16_SFLOAT")
+ return FormatType::kR16G16_SFLOAT;
+ if (data == "R16G16_SINT")
+ return FormatType::kR16G16_SINT;
+ if (data == "R16G16_SNORM")
+ return FormatType::kR16G16_SNORM;
+ if (data == "R16G16_SSCALED")
+ return FormatType::kR16G16_SSCALED;
+ if (data == "R16G16_UINT")
+ return FormatType::kR16G16_UINT;
+ if (data == "R16G16_UNORM")
+ return FormatType::kR16G16_UNORM;
+ if (data == "R16G16_USCALED")
+ return FormatType::kR16G16_USCALED;
+ if (data == "R16_SFLOAT")
+ return FormatType::kR16_SFLOAT;
+ if (data == "R16_SINT")
+ return FormatType::kR16_SINT;
+ if (data == "R16_SNORM")
+ return FormatType::kR16_SNORM;
+ if (data == "R16_SSCALED")
+ return FormatType::kR16_SSCALED;
+ if (data == "R16_UINT")
+ return FormatType::kR16_UINT;
+ if (data == "R16_UNORM")
+ return FormatType::kR16_UNORM;
+ if (data == "R16_USCALED")
+ return FormatType::kR16_USCALED;
+ if (data == "R32G32B32A32_SFLOAT")
+ return FormatType::kR32G32B32A32_SFLOAT;
+ if (data == "R32G32B32A32_SINT")
+ return FormatType::kR32G32B32A32_SINT;
+ if (data == "R32G32B32A32_UINT")
+ return FormatType::kR32G32B32A32_UINT;
+ if (data == "R32G32B32_SFLOAT")
+ return FormatType::kR32G32B32_SFLOAT;
+ if (data == "R32G32B32_SINT")
+ return FormatType::kR32G32B32_SINT;
+ if (data == "R32G32B32_UINT")
+ return FormatType::kR32G32B32_UINT;
+ if (data == "R32G32_SFLOAT")
+ return FormatType::kR32G32_SFLOAT;
+ if (data == "R32G32_SINT")
+ return FormatType::kR32G32_SINT;
+ if (data == "R32G32_UINT")
+ return FormatType::kR32G32_UINT;
+ if (data == "R32_SFLOAT")
+ return FormatType::kR32_SFLOAT;
+ if (data == "R32_SINT")
+ return FormatType::kR32_SINT;
+ if (data == "R32_UINT")
+ return FormatType::kR32_UINT;
+ if (data == "R4G4B4A4_UNORM_PACK16")
+ return FormatType::kR4G4B4A4_UNORM_PACK16;
+ if (data == "R4G4_UNORM_PACK8")
+ return FormatType::kR4G4_UNORM_PACK8;
+ if (data == "R5G5B5A1_UNORM_PACK16")
+ return FormatType::kR5G5B5A1_UNORM_PACK16;
+ if (data == "R5G6B5_UNORM_PACK16")
+ return FormatType::kR5G6B5_UNORM_PACK16;
+ if (data == "R64G64B64A64_SFLOAT")
+ return FormatType::kR64G64B64A64_SFLOAT;
+ if (data == "R64G64B64A64_SINT")
+ return FormatType::kR64G64B64A64_SINT;
+ if (data == "R64G64B64A64_UINT")
+ return FormatType::kR64G64B64A64_UINT;
+ if (data == "R64G64B64_SFLOAT")
+ return FormatType::kR64G64B64_SFLOAT;
+ if (data == "R64G64B64_SINT")
+ return FormatType::kR64G64B64_SINT;
+ if (data == "R64G64B64_UINT")
+ return FormatType::kR64G64B64_UINT;
+ if (data == "R64G64_SFLOAT")
+ return FormatType::kR64G64_SFLOAT;
+ if (data == "R64G64_SINT")
+ return FormatType::kR64G64_SINT;
+ if (data == "R64G64_UINT")
+ return FormatType::kR64G64_UINT;
+ if (data == "R64_SFLOAT")
+ return FormatType::kR64_SFLOAT;
+ if (data == "R64_SINT")
+ return FormatType::kR64_SINT;
+ if (data == "R64_UINT")
+ return FormatType::kR64_UINT;
+ if (data == "R8G8B8A8_SINT")
+ return FormatType::kR8G8B8A8_SINT;
+ if (data == "R8G8B8A8_SNORM")
+ return FormatType::kR8G8B8A8_SNORM;
+ if (data == "R8G8B8A8_SRGB")
+ return FormatType::kR8G8B8A8_SRGB;
+ if (data == "R8G8B8A8_SSCALED")
+ return FormatType::kR8G8B8A8_SSCALED;
+ if (data == "R8G8B8A8_UINT")
+ return FormatType::kR8G8B8A8_UINT;
+ if (data == "R8G8B8A8_UNORM")
+ return FormatType::kR8G8B8A8_UNORM;
+ if (data == "R8G8B8A8_USCALED")
+ return FormatType::kR8G8B8A8_USCALED;
+ if (data == "R8G8B8_SINT")
+ return FormatType::kR8G8B8_SINT;
+ if (data == "R8G8B8_SNORM")
+ return FormatType::kR8G8B8_SNORM;
+ if (data == "R8G8B8_SRGB")
+ return FormatType::kR8G8B8_SRGB;
+ if (data == "R8G8B8_SSCALED")
+ return FormatType::kR8G8B8_SSCALED;
+ if (data == "R8G8B8_UINT")
+ return FormatType::kR8G8B8_UINT;
+ if (data == "R8G8B8_UNORM")
+ return FormatType::kR8G8B8_UNORM;
+ if (data == "R8G8B8_USCALED")
+ return FormatType::kR8G8B8_USCALED;
+ if (data == "R8G8_SINT")
+ return FormatType::kR8G8_SINT;
+ if (data == "R8G8_SNORM")
+ return FormatType::kR8G8_SNORM;
+ if (data == "R8G8_SRGB")
+ return FormatType::kR8G8_SRGB;
+ if (data == "R8G8_SSCALED")
+ return FormatType::kR8G8_SSCALED;
+ if (data == "R8G8_UINT")
+ return FormatType::kR8G8_UINT;
+ if (data == "R8G8_UNORM")
+ return FormatType::kR8G8_UNORM;
+ if (data == "R8G8_USCALED")
+ return FormatType::kR8G8_USCALED;
+ if (data == "R8_SINT")
+ return FormatType::kR8_SINT;
+ if (data == "R8_SNORM")
+ return FormatType::kR8_SNORM;
+ if (data == "R8_SRGB")
+ return FormatType::kR8_SRGB;
+ if (data == "R8_SSCALED")
+ return FormatType::kR8_SSCALED;
+ if (data == "R8_UINT")
+ return FormatType::kR8_UINT;
+ if (data == "R8_UNORM")
+ return FormatType::kR8_UNORM;
+ if (data == "R8_USCALED")
+ return FormatType::kR8_USCALED;
+ if (data == "S8_UINT")
+ return FormatType::kS8_UINT;
+ if (data == "X8_D24_UNORM_PACK32")
+ return FormatType::kX8_D24_UNORM_PACK32;
+
+ return FormatType::kUnknown;
+}
+
+std::unique_ptr<Format> FormatParser::ParseGlslFormat(const std::string& fmt) {
+ size_t pos = fmt.find('/');
+ std::string gl_type = fmt.substr(0, pos);
+ std::string glsl_type = fmt.substr(pos + 1);
+
+ uint8_t bits = 0;
+ FormatMode mode = FormatMode::kUNorm;
+
+ static const struct {
+ const char* name;
+ uint8_t bits;
+ bool is_signed;
+ bool is_int;
+ } types[] = {
+ {"byte", 8, true, true}, {"ubyte", 8, false, true},
+ {"short", 16, true, true}, {"ushort", 16, false, true},
+ {"int", 32, true, true}, {"uint", 32, false, true},
+ {"half", 16, true, false}, {"float", 32, true, false},
+ {"double", 64, true, false},
+ };
+ for (auto& type : types) {
+ if (gl_type == std::string(type.name)) {
+ if (type.is_int)
+ mode = type.is_signed ? FormatMode::kSInt : FormatMode::kUInt;
+ else
+ mode = FormatMode::kSFloat;
+
+ bits = type.bits;
+ }
+ }
+
+ // Failed to find gl type.
+ if (mode == FormatMode::kUNorm)
+ return nullptr;
+
+ if (fmt.length() < 4)
+ return nullptr;
+
+ int8_t num_components = 0;
+ if (glsl_type == "float" || glsl_type == "double" || glsl_type == "int" ||
+ glsl_type == "uint") {
+ num_components = 1;
+ } else if (glsl_type.substr(0, 3) == "vec") {
+ num_components =
+ static_cast<int8_t>(strtol(glsl_type.c_str() + 3, nullptr, 10));
+ if (num_components < 2)
+ return nullptr;
+ } else if ((glsl_type[0] == 'd' || glsl_type[0] == 'i' ||
+ glsl_type[0] == 'u') &&
+ glsl_type.substr(1, 3) == "vec") {
+ num_components =
+ static_cast<int8_t>(strtol(glsl_type.c_str() + 4, nullptr, 10));
+ if (num_components < 2)
+ return nullptr;
+ }
+ if (num_components > 4)
+ return nullptr;
+
+ std::string new_name = "R" + std::to_string(bits);
+ --num_components;
+
+ if (num_components-- > 0)
+ new_name += "G" + std::to_string(bits);
+ if (num_components-- > 0)
+ new_name += "B" + std::to_string(bits);
+ if (num_components-- > 0)
+ new_name += "A" + std::to_string(bits);
+
+ new_name += "_";
+ if (mode == FormatMode::kSInt)
+ new_name += "SINT";
+ else if (mode == FormatMode::kUInt)
+ new_name += "UINT";
+ else if (mode == FormatMode::kSFloat)
+ new_name += "SFLOAT";
+ else
+ return nullptr;
+
+ return Parse(new_name);
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/format_parser.h b/src/vkscript/format_parser.h
new file mode 100644
index 0000000..327ade3
--- /dev/null
+++ b/src/vkscript/format_parser.h
@@ -0,0 +1,56 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_FORMAT_PARSER_H_
+#define SRC_VKSCRIPT_FORMAT_PARSER_H_
+
+#include <memory>
+#include <string>
+
+#include "src/format.h"
+
+namespace amber {
+
+class Format;
+
+namespace vkscript {
+
+class FormatParser {
+ public:
+ FormatParser();
+ ~FormatParser();
+
+ std::unique_ptr<Format> Parse(const std::string& fmt);
+
+ private:
+ std::unique_ptr<Format> ParseGlslFormat(const std::string& fmt);
+ void ProcessChunk(Format*, const std::string&);
+ FormatType NameToType(const std::string& data);
+ void AddPiece(FormatComponentType type, uint8_t bits);
+ void FlushPieces(Format* fmt, FormatMode mode);
+
+ struct Pieces {
+ Pieces(FormatComponentType t, uint8_t bits) : type(t), num_bits(bits) {}
+
+ FormatComponentType type;
+ uint8_t num_bits;
+ };
+
+ std::vector<Pieces> pieces_;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_FORMAT_PARSER_H_
diff --git a/src/vkscript/format_parser_test.cc b/src/vkscript/format_parser_test.cc
new file mode 100644
index 0000000..d5bee85
--- /dev/null
+++ b/src/vkscript/format_parser_test.cc
@@ -0,0 +1,1278 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/format_parser.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+namespace vkscript {
+
+using FormatParserTest = testing::Test;
+
+TEST_F(FormatParserTest, Formats) {
+ struct {
+ const char* name;
+ FormatType type;
+ uint8_t pack_size;
+ uint8_t component_count;
+ struct {
+ FormatComponentType type;
+ FormatMode mode;
+ uint8_t num_bits;
+ } components[4];
+ } formats[] = {
+ {"A1R5G5B5_UNORM_PACK16",
+ FormatType::kA1R5G5B5_UNORM_PACK16,
+ 16U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUNorm, 1},
+ {FormatComponentType::kR, FormatMode::kUNorm, 5},
+ {FormatComponentType::kG, FormatMode::kUNorm, 5},
+ {FormatComponentType::kB, FormatMode::kUNorm, 5},
+ }},
+ {"A2B10G10R10_SINT_PACK32",
+ FormatType::kA2B10G10R10_SINT_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSInt, 2},
+ {FormatComponentType::kB, FormatMode::kSInt, 10},
+ {FormatComponentType::kG, FormatMode::kSInt, 10},
+ {FormatComponentType::kR, FormatMode::kSInt, 10},
+ }},
+ {"A2B10G10R10_SNORM_PACK32",
+ FormatType::kA2B10G10R10_SNORM_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSNorm, 2},
+ {FormatComponentType::kB, FormatMode::kSNorm, 10},
+ {FormatComponentType::kG, FormatMode::kSNorm, 10},
+ {FormatComponentType::kR, FormatMode::kSNorm, 10},
+ }},
+ {"A2B10G10R10_SSCALED_PACK32",
+ FormatType::kA2B10G10R10_SSCALED_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSScaled, 2},
+ {FormatComponentType::kB, FormatMode::kSScaled, 10},
+ {FormatComponentType::kG, FormatMode::kSScaled, 10},
+ {FormatComponentType::kR, FormatMode::kSScaled, 10},
+ }},
+ {"A2B10G10R10_UINT_PACK32",
+ FormatType::kA2B10G10R10_UINT_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUInt, 2},
+ {FormatComponentType::kB, FormatMode::kUInt, 10},
+ {FormatComponentType::kG, FormatMode::kUInt, 10},
+ {FormatComponentType::kR, FormatMode::kUInt, 10},
+ }},
+ {"A2B10G10R10_UNORM_PACK32",
+ FormatType::kA2B10G10R10_UNORM_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUNorm, 2},
+ {FormatComponentType::kB, FormatMode::kUNorm, 10},
+ {FormatComponentType::kG, FormatMode::kUNorm, 10},
+ {FormatComponentType::kR, FormatMode::kUNorm, 10},
+ }},
+ {"A2B10G10R10_USCALED_PACK32",
+ FormatType::kA2B10G10R10_USCALED_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUScaled, 2},
+ {FormatComponentType::kB, FormatMode::kUScaled, 10},
+ {FormatComponentType::kG, FormatMode::kUScaled, 10},
+ {FormatComponentType::kR, FormatMode::kUScaled, 10},
+ }},
+ {"A2R10G10B10_SINT_PACK32",
+ FormatType::kA2R10G10B10_SINT_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSInt, 2},
+ {FormatComponentType::kR, FormatMode::kSInt, 10},
+ {FormatComponentType::kG, FormatMode::kSInt, 10},
+ {FormatComponentType::kB, FormatMode::kSInt, 10},
+ }},
+ {"A2R10G10B10_SNORM_PACK32",
+ FormatType::kA2R10G10B10_SNORM_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSNorm, 2},
+ {FormatComponentType::kR, FormatMode::kSNorm, 10},
+ {FormatComponentType::kG, FormatMode::kSNorm, 10},
+ {FormatComponentType::kB, FormatMode::kSNorm, 10},
+ }},
+ {"A2R10G10B10_SSCALED_PACK32",
+ FormatType::kA2R10G10B10_SSCALED_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSScaled, 2},
+ {FormatComponentType::kR, FormatMode::kSScaled, 10},
+ {FormatComponentType::kG, FormatMode::kSScaled, 10},
+ {FormatComponentType::kB, FormatMode::kSScaled, 10},
+ }},
+ {"A2R10G10B10_UINT_PACK32",
+ FormatType::kA2R10G10B10_UINT_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUInt, 2},
+ {FormatComponentType::kR, FormatMode::kUInt, 10},
+ {FormatComponentType::kG, FormatMode::kUInt, 10},
+ {FormatComponentType::kB, FormatMode::kUInt, 10},
+ }},
+ {"A2R10G10B10_UNORM_PACK32",
+ FormatType::kA2R10G10B10_UNORM_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUNorm, 2},
+ {FormatComponentType::kR, FormatMode::kUNorm, 10},
+ {FormatComponentType::kG, FormatMode::kUNorm, 10},
+ {FormatComponentType::kB, FormatMode::kUNorm, 10},
+ }},
+ {"A2R10G10B10_USCALED_PACK32",
+ FormatType::kA2R10G10B10_USCALED_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUScaled, 2},
+ {FormatComponentType::kR, FormatMode::kUScaled, 10},
+ {FormatComponentType::kG, FormatMode::kUScaled, 10},
+ {FormatComponentType::kB, FormatMode::kUScaled, 10},
+ }},
+ {"A8B8G8R8_SINT_PACK32",
+ FormatType::kA8B8G8R8_SINT_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSInt, 8},
+ {FormatComponentType::kB, FormatMode::kSInt, 8},
+ {FormatComponentType::kG, FormatMode::kSInt, 8},
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ }},
+ {"A8B8G8R8_SNORM_PACK32",
+ FormatType::kA8B8G8R8_SNORM_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSNorm, 8},
+ {FormatComponentType::kB, FormatMode::kSNorm, 8},
+ {FormatComponentType::kG, FormatMode::kSNorm, 8},
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ }},
+ {"A8B8G8R8_SRGB_PACK32",
+ FormatType::kA8B8G8R8_SRGB_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSRGB, 8},
+ {FormatComponentType::kB, FormatMode::kSRGB, 8},
+ {FormatComponentType::kG, FormatMode::kSRGB, 8},
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ }},
+ {"A8B8G8R8_SSCALED_PACK32",
+ FormatType::kA8B8G8R8_SSCALED_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kSScaled, 8},
+ {FormatComponentType::kB, FormatMode::kSScaled, 8},
+ {FormatComponentType::kG, FormatMode::kSScaled, 8},
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ }},
+ {"A8B8G8R8_UINT_PACK32",
+ FormatType::kA8B8G8R8_UINT_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUInt, 8},
+ {FormatComponentType::kB, FormatMode::kUInt, 8},
+ {FormatComponentType::kG, FormatMode::kUInt, 8},
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ }},
+ {"A8B8G8R8_UNORM_PACK32",
+ FormatType::kA8B8G8R8_UNORM_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUNorm, 8},
+ {FormatComponentType::kB, FormatMode::kUNorm, 8},
+ {FormatComponentType::kG, FormatMode::kUNorm, 8},
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ }},
+ {"A8B8G8R8_USCALED_PACK32",
+ FormatType::kA8B8G8R8_USCALED_PACK32,
+ 32U,
+ 4U,
+ {
+ {FormatComponentType::kA, FormatMode::kUScaled, 8},
+ {FormatComponentType::kB, FormatMode::kUScaled, 8},
+ {FormatComponentType::kG, FormatMode::kUScaled, 8},
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ }},
+ {"B10G11R11_UFLOAT_PACK32",
+ FormatType::kB10G11R11_UFLOAT_PACK32,
+ 32U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kUFloat, 10},
+ {FormatComponentType::kG, FormatMode::kUFloat, 11},
+ {FormatComponentType::kR, FormatMode::kUFloat, 11},
+ }},
+ {"B4G4R4A4_UNORM_PACK16",
+ FormatType::kB4G4R4A4_UNORM_PACK16,
+ 16U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kUNorm, 4},
+ {FormatComponentType::kG, FormatMode::kUNorm, 4},
+ {FormatComponentType::kR, FormatMode::kUNorm, 4},
+ {FormatComponentType::kA, FormatMode::kUNorm, 4},
+ }},
+ {"B5G5R5A1_UNORM_PACK16",
+ FormatType::kB5G5R5A1_UNORM_PACK16,
+ 16U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kUNorm, 5},
+ {FormatComponentType::kG, FormatMode::kUNorm, 5},
+ {FormatComponentType::kR, FormatMode::kUNorm, 5},
+ {FormatComponentType::kA, FormatMode::kUNorm, 1},
+ }},
+ {"B5G6R5_UNORM_PACK16",
+ FormatType::kB5G6R5_UNORM_PACK16,
+ 16U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kUNorm, 5},
+ {FormatComponentType::kG, FormatMode::kUNorm, 6},
+ {FormatComponentType::kR, FormatMode::kUNorm, 5},
+ }},
+ {"B8G8R8A8_SINT",
+ FormatType::kB8G8R8A8_SINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kSInt, 8},
+ {FormatComponentType::kG, FormatMode::kSInt, 8},
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ {FormatComponentType::kA, FormatMode::kSInt, 8},
+ }},
+ {"B8G8R8A8_SNORM",
+ FormatType::kB8G8R8A8_SNORM,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kSNorm, 8},
+ {FormatComponentType::kG, FormatMode::kSNorm, 8},
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ {FormatComponentType::kA, FormatMode::kSNorm, 8},
+ }},
+ {"B8G8R8A8_SRGB",
+ FormatType::kB8G8R8A8_SRGB,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kSRGB, 8},
+ {FormatComponentType::kG, FormatMode::kSRGB, 8},
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ {FormatComponentType::kA, FormatMode::kSRGB, 8},
+ }},
+ {"B8G8R8A8_SSCALED",
+ FormatType::kB8G8R8A8_SSCALED,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kSScaled, 8},
+ {FormatComponentType::kG, FormatMode::kSScaled, 8},
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ {FormatComponentType::kA, FormatMode::kSScaled, 8},
+ }},
+ {"B8G8R8A8_UINT",
+ FormatType::kB8G8R8A8_UINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kUInt, 8},
+ {FormatComponentType::kG, FormatMode::kUInt, 8},
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ {FormatComponentType::kA, FormatMode::kUInt, 8},
+ }},
+ {"B8G8R8A8_UNORM",
+ FormatType::kB8G8R8A8_UNORM,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kUNorm, 8},
+ {FormatComponentType::kG, FormatMode::kUNorm, 8},
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ {FormatComponentType::kA, FormatMode::kUNorm, 8},
+ }},
+ {"B8G8R8A8_USCALED",
+ FormatType::kB8G8R8A8_USCALED,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kB, FormatMode::kUScaled, 8},
+ {FormatComponentType::kG, FormatMode::kUScaled, 8},
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ {FormatComponentType::kA, FormatMode::kUScaled, 8},
+ }},
+ {"B8G8R8_SINT",
+ FormatType::kB8G8R8_SINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kSInt, 8},
+ {FormatComponentType::kG, FormatMode::kSInt, 8},
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ }},
+ {"B8G8R8_SNORM",
+ FormatType::kB8G8R8_SNORM,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kSNorm, 8},
+ {FormatComponentType::kG, FormatMode::kSNorm, 8},
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ }},
+ {"B8G8R8_SRGB",
+ FormatType::kB8G8R8_SRGB,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kSRGB, 8},
+ {FormatComponentType::kG, FormatMode::kSRGB, 8},
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ }},
+ {"B8G8R8_SSCALED",
+ FormatType::kB8G8R8_SSCALED,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kSScaled, 8},
+ {FormatComponentType::kG, FormatMode::kSScaled, 8},
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ }},
+ {"B8G8R8_UINT",
+ FormatType::kB8G8R8_UINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kUInt, 8},
+ {FormatComponentType::kG, FormatMode::kUInt, 8},
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ }},
+ {"B8G8R8_UNORM",
+ FormatType::kB8G8R8_UNORM,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kUNorm, 8},
+ {FormatComponentType::kG, FormatMode::kUNorm, 8},
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ }},
+ {"B8G8R8_USCALED",
+ FormatType::kB8G8R8_USCALED,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kB, FormatMode::kUScaled, 8},
+ {FormatComponentType::kG, FormatMode::kUScaled, 8},
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ }},
+ {"D16_UNORM",
+ FormatType::kD16_UNORM,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kD, FormatMode::kUNorm, 16},
+ }},
+ {"D16_UNORM_S8_UINT",
+ FormatType::kD16_UNORM_S8_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kD, FormatMode::kUNorm, 16},
+ {FormatComponentType::kS, FormatMode::kUInt, 8},
+ }},
+ {"D24_UNORM_S8_UINT",
+ FormatType::kD24_UNORM_S8_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kD, FormatMode::kUNorm, 24},
+ {FormatComponentType::kS, FormatMode::kUInt, 8},
+ }},
+ {"D32_SFLOAT",
+ FormatType::kD32_SFLOAT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kD, FormatMode::kSFloat, 32},
+ }},
+ {"D32_SFLOAT_S8_UINT",
+ FormatType::kD32_SFLOAT_S8_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kD, FormatMode::kSFloat, 32},
+ {FormatComponentType::kS, FormatMode::kUInt, 8},
+ }},
+ {"R16G16B16A16_SFLOAT",
+ FormatType::kR16G16B16A16_SFLOAT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 16},
+ {FormatComponentType::kG, FormatMode::kSFloat, 16},
+ {FormatComponentType::kB, FormatMode::kSFloat, 16},
+ {FormatComponentType::kA, FormatMode::kSFloat, 16},
+ }},
+ {"R16G16B16A16_SINT",
+ FormatType::kR16G16B16A16_SINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 16},
+ {FormatComponentType::kG, FormatMode::kSInt, 16},
+ {FormatComponentType::kB, FormatMode::kSInt, 16},
+ {FormatComponentType::kA, FormatMode::kSInt, 16},
+ }},
+ {"R16G16B16A16_SNORM",
+ FormatType::kR16G16B16A16_SNORM,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 16},
+ {FormatComponentType::kG, FormatMode::kSNorm, 16},
+ {FormatComponentType::kB, FormatMode::kSNorm, 16},
+ {FormatComponentType::kA, FormatMode::kSNorm, 16},
+ }},
+ {"R16G16B16A16_SSCALED",
+ FormatType::kR16G16B16A16_SSCALED,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 16},
+ {FormatComponentType::kG, FormatMode::kSScaled, 16},
+ {FormatComponentType::kB, FormatMode::kSScaled, 16},
+ {FormatComponentType::kA, FormatMode::kSScaled, 16},
+ }},
+ {"R16G16B16A16_UINT",
+ FormatType::kR16G16B16A16_UINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 16},
+ {FormatComponentType::kG, FormatMode::kUInt, 16},
+ {FormatComponentType::kB, FormatMode::kUInt, 16},
+ {FormatComponentType::kA, FormatMode::kUInt, 16},
+ }},
+ {"R16G16B16A16_UNORM",
+ FormatType::kR16G16B16A16_UNORM,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 16},
+ {FormatComponentType::kG, FormatMode::kUNorm, 16},
+ {FormatComponentType::kB, FormatMode::kUNorm, 16},
+ {FormatComponentType::kA, FormatMode::kUNorm, 16},
+ }},
+ {"R16G16B16A16_USCALED",
+ FormatType::kR16G16B16A16_USCALED,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 16},
+ {FormatComponentType::kG, FormatMode::kUScaled, 16},
+ {FormatComponentType::kB, FormatMode::kUScaled, 16},
+ {FormatComponentType::kA, FormatMode::kUScaled, 16},
+ }},
+ {"R16G16B16_SFLOAT",
+ FormatType::kR16G16B16_SFLOAT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 16},
+ {FormatComponentType::kG, FormatMode::kSFloat, 16},
+ {FormatComponentType::kB, FormatMode::kSFloat, 16},
+ }},
+ {"R16G16B16_SINT",
+ FormatType::kR16G16B16_SINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 16},
+ {FormatComponentType::kG, FormatMode::kSInt, 16},
+ {FormatComponentType::kB, FormatMode::kSInt, 16},
+ }},
+ {"R16G16B16_SNORM",
+ FormatType::kR16G16B16_SNORM,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 16},
+ {FormatComponentType::kG, FormatMode::kSNorm, 16},
+ {FormatComponentType::kB, FormatMode::kSNorm, 16},
+ }},
+ {"R16G16B16_SSCALED",
+ FormatType::kR16G16B16_SSCALED,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 16},
+ {FormatComponentType::kG, FormatMode::kSScaled, 16},
+ {FormatComponentType::kB, FormatMode::kSScaled, 16},
+ }},
+ {"R16G16B16_UINT",
+ FormatType::kR16G16B16_UINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 16},
+ {FormatComponentType::kG, FormatMode::kUInt, 16},
+ {FormatComponentType::kB, FormatMode::kUInt, 16},
+ }},
+ {"R16G16B16_UNORM",
+ FormatType::kR16G16B16_UNORM,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 16},
+ {FormatComponentType::kG, FormatMode::kUNorm, 16},
+ {FormatComponentType::kB, FormatMode::kUNorm, 16},
+ }},
+ {"R16G16B16_USCALED",
+ FormatType::kR16G16B16_USCALED,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 16},
+ {FormatComponentType::kG, FormatMode::kUScaled, 16},
+ {FormatComponentType::kB, FormatMode::kUScaled, 16},
+ }},
+ {"R16G16_SFLOAT",
+ FormatType::kR16G16_SFLOAT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 16},
+ {FormatComponentType::kG, FormatMode::kSFloat, 16},
+ }},
+ {"R16G16_SINT",
+ FormatType::kR16G16_SINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 16},
+ {FormatComponentType::kG, FormatMode::kSInt, 16},
+ }},
+ {"R16G16_SNORM",
+ FormatType::kR16G16_SNORM,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 16},
+ {FormatComponentType::kG, FormatMode::kSNorm, 16},
+ }},
+ {"R16G16_SSCALED",
+ FormatType::kR16G16_SSCALED,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 16},
+ {FormatComponentType::kG, FormatMode::kSScaled, 16},
+ }},
+ {"R16G16_UINT",
+ FormatType::kR16G16_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 16},
+ {FormatComponentType::kG, FormatMode::kUInt, 16},
+ }},
+ {"R16G16_UNORM",
+ FormatType::kR16G16_UNORM,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 16},
+ {FormatComponentType::kG, FormatMode::kUNorm, 16},
+ }},
+ {"R16G16_USCALED",
+ FormatType::kR16G16_USCALED,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 16},
+ {FormatComponentType::kG, FormatMode::kUScaled, 16},
+ }},
+ {"R16_SFLOAT",
+ FormatType::kR16_SFLOAT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 16},
+ }},
+ {"R16_SINT",
+ FormatType::kR16_SINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 16},
+ }},
+ {"R16_SNORM",
+ FormatType::kR16_SNORM,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 16},
+ }},
+ {"R16_SSCALED",
+ FormatType::kR16_SSCALED,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 16},
+ }},
+ {"R16_UINT",
+ FormatType::kR16_UINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 16},
+ }},
+ {"R16_UNORM",
+ FormatType::kR16_UNORM,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 16},
+ }},
+ {"R16_USCALED",
+ FormatType::kR16_USCALED,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 16},
+ }},
+ {"R32G32B32A32_SFLOAT",
+ FormatType::kR32G32B32A32_SFLOAT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 32},
+ {FormatComponentType::kG, FormatMode::kSFloat, 32},
+ {FormatComponentType::kB, FormatMode::kSFloat, 32},
+ {FormatComponentType::kA, FormatMode::kSFloat, 32},
+ }},
+ {"R32G32B32A32_SINT",
+ FormatType::kR32G32B32A32_SINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 32},
+ {FormatComponentType::kG, FormatMode::kSInt, 32},
+ {FormatComponentType::kB, FormatMode::kSInt, 32},
+ {FormatComponentType::kA, FormatMode::kSInt, 32},
+ }},
+ {"R32G32B32A32_UINT",
+ FormatType::kR32G32B32A32_UINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 32},
+ {FormatComponentType::kG, FormatMode::kUInt, 32},
+ {FormatComponentType::kB, FormatMode::kUInt, 32},
+ {FormatComponentType::kA, FormatMode::kUInt, 32},
+ }},
+ {"R32G32B32_SFLOAT",
+ FormatType::kR32G32B32_SFLOAT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 32},
+ {FormatComponentType::kG, FormatMode::kSFloat, 32},
+ {FormatComponentType::kB, FormatMode::kSFloat, 32},
+ }},
+ {"R32G32B32_SINT",
+ FormatType::kR32G32B32_SINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 32},
+ {FormatComponentType::kG, FormatMode::kSInt, 32},
+ {FormatComponentType::kB, FormatMode::kSInt, 32},
+ }},
+ {"R32G32B32_UINT",
+ FormatType::kR32G32B32_UINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 32},
+ {FormatComponentType::kG, FormatMode::kUInt, 32},
+ {FormatComponentType::kB, FormatMode::kUInt, 32},
+ }},
+ {"R32G32_SFLOAT",
+ FormatType::kR32G32_SFLOAT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 32},
+ {FormatComponentType::kG, FormatMode::kSFloat, 32},
+ }},
+ {"R32G32_SINT",
+ FormatType::kR32G32_SINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 32},
+ {FormatComponentType::kG, FormatMode::kSInt, 32},
+ }},
+ {"R32G32_UINT",
+ FormatType::kR32G32_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 32},
+ {FormatComponentType::kG, FormatMode::kUInt, 32},
+ }},
+ {"R32_SFLOAT",
+ FormatType::kR32_SFLOAT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 32},
+ }},
+ {"R32_SINT",
+ FormatType::kR32_SINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 32},
+ }},
+ {"R32_UINT",
+ FormatType::kR32_UINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 32},
+ }},
+ {"R4G4B4A4_UNORM_PACK16",
+ FormatType::kR4G4B4A4_UNORM_PACK16,
+ 16U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 4},
+ {FormatComponentType::kG, FormatMode::kUNorm, 4},
+ {FormatComponentType::kB, FormatMode::kUNorm, 4},
+ {FormatComponentType::kA, FormatMode::kUNorm, 4},
+ }},
+ {"R4G4_UNORM_PACK8",
+ FormatType::kR4G4_UNORM_PACK8,
+ 8U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 4},
+ {FormatComponentType::kG, FormatMode::kUNorm, 4},
+ }},
+ {"R5G5B5A1_UNORM_PACK16",
+ FormatType::kR5G5B5A1_UNORM_PACK16,
+ 16U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 5},
+ {FormatComponentType::kG, FormatMode::kUNorm, 5},
+ {FormatComponentType::kB, FormatMode::kUNorm, 5},
+ {FormatComponentType::kA, FormatMode::kUNorm, 1},
+ }},
+ {"R5G6B5_UNORM_PACK16",
+ FormatType::kR5G6B5_UNORM_PACK16,
+ 16U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 5},
+ {FormatComponentType::kG, FormatMode::kUNorm, 6},
+ {FormatComponentType::kB, FormatMode::kUNorm, 5},
+ }},
+ {"R64G64B64A64_SFLOAT",
+ FormatType::kR64G64B64A64_SFLOAT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 64},
+ {FormatComponentType::kG, FormatMode::kSFloat, 64},
+ {FormatComponentType::kB, FormatMode::kSFloat, 64},
+ {FormatComponentType::kA, FormatMode::kSFloat, 64},
+ }},
+ {"R64G64B64A64_SINT",
+ FormatType::kR64G64B64A64_SINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 64},
+ {FormatComponentType::kG, FormatMode::kSInt, 64},
+ {FormatComponentType::kB, FormatMode::kSInt, 64},
+ {FormatComponentType::kA, FormatMode::kSInt, 64},
+ }},
+ {"R64G64B64A64_UINT",
+ FormatType::kR64G64B64A64_UINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 64},
+ {FormatComponentType::kG, FormatMode::kUInt, 64},
+ {FormatComponentType::kB, FormatMode::kUInt, 64},
+ {FormatComponentType::kA, FormatMode::kUInt, 64},
+ }},
+ {"R64G64B64_SFLOAT",
+ FormatType::kR64G64B64_SFLOAT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 64},
+ {FormatComponentType::kG, FormatMode::kSFloat, 64},
+ {FormatComponentType::kB, FormatMode::kSFloat, 64},
+ }},
+ {"R64G64B64_SINT",
+ FormatType::kR64G64B64_SINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 64},
+ {FormatComponentType::kG, FormatMode::kSInt, 64},
+ {FormatComponentType::kB, FormatMode::kSInt, 64},
+ }},
+ {"R64G64B64_UINT",
+ FormatType::kR64G64B64_UINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 64},
+ {FormatComponentType::kG, FormatMode::kUInt, 64},
+ {FormatComponentType::kB, FormatMode::kUInt, 64},
+ }},
+ {"R64G64_SFLOAT",
+ FormatType::kR64G64_SFLOAT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 64},
+ {FormatComponentType::kG, FormatMode::kSFloat, 64},
+ }},
+ {"R64G64_SINT",
+ FormatType::kR64G64_SINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 64},
+ {FormatComponentType::kG, FormatMode::kSInt, 64},
+ }},
+ {"R64G64_UINT",
+ FormatType::kR64G64_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 64},
+ {FormatComponentType::kG, FormatMode::kUInt, 64},
+ }},
+ {"R64_SFLOAT",
+ FormatType::kR64_SFLOAT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSFloat, 64},
+ }},
+ {"R64_SINT",
+ FormatType::kR64_SINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 64},
+ }},
+ {"R64_UINT",
+ FormatType::kR64_UINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 64},
+ }},
+ {"R8G8B8A8_SINT",
+ FormatType::kR8G8B8A8_SINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ {FormatComponentType::kG, FormatMode::kSInt, 8},
+ {FormatComponentType::kB, FormatMode::kSInt, 8},
+ {FormatComponentType::kA, FormatMode::kSInt, 8},
+ }},
+ {"R8G8B8A8_SNORM",
+ FormatType::kR8G8B8A8_SNORM,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ {FormatComponentType::kG, FormatMode::kSNorm, 8},
+ {FormatComponentType::kB, FormatMode::kSNorm, 8},
+ {FormatComponentType::kA, FormatMode::kSNorm, 8},
+ }},
+ {"R8G8B8A8_SRGB",
+ FormatType::kR8G8B8A8_SRGB,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ {FormatComponentType::kG, FormatMode::kSRGB, 8},
+ {FormatComponentType::kB, FormatMode::kSRGB, 8},
+ {FormatComponentType::kA, FormatMode::kSRGB, 8},
+ }},
+ {"R8G8B8A8_SSCALED",
+ FormatType::kR8G8B8A8_SSCALED,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ {FormatComponentType::kG, FormatMode::kSScaled, 8},
+ {FormatComponentType::kB, FormatMode::kSScaled, 8},
+ {FormatComponentType::kA, FormatMode::kSScaled, 8},
+ }},
+ {"R8G8B8A8_UINT",
+ FormatType::kR8G8B8A8_UINT,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ {FormatComponentType::kG, FormatMode::kUInt, 8},
+ {FormatComponentType::kB, FormatMode::kUInt, 8},
+ {FormatComponentType::kA, FormatMode::kUInt, 8},
+ }},
+ {"R8G8B8A8_UNORM",
+ FormatType::kR8G8B8A8_UNORM,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ {FormatComponentType::kG, FormatMode::kUNorm, 8},
+ {FormatComponentType::kB, FormatMode::kUNorm, 8},
+ {FormatComponentType::kA, FormatMode::kUNorm, 8},
+ }},
+ {"R8G8B8A8_USCALED",
+ FormatType::kR8G8B8A8_USCALED,
+ 0U,
+ 4U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ {FormatComponentType::kG, FormatMode::kUScaled, 8},
+ {FormatComponentType::kB, FormatMode::kUScaled, 8},
+ {FormatComponentType::kA, FormatMode::kUScaled, 8},
+ }},
+ {"R8G8B8_SINT",
+ FormatType::kR8G8B8_SINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ {FormatComponentType::kG, FormatMode::kSInt, 8},
+ {FormatComponentType::kB, FormatMode::kSInt, 8},
+ }},
+ {"R8G8B8_SNORM",
+ FormatType::kR8G8B8_SNORM,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ {FormatComponentType::kG, FormatMode::kSNorm, 8},
+ {FormatComponentType::kB, FormatMode::kSNorm, 8},
+ }},
+ {"R8G8B8_SRGB",
+ FormatType::kR8G8B8_SRGB,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ {FormatComponentType::kG, FormatMode::kSRGB, 8},
+ {FormatComponentType::kB, FormatMode::kSRGB, 8},
+ }},
+ {"R8G8B8_SSCALED",
+ FormatType::kR8G8B8_SSCALED,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ {FormatComponentType::kG, FormatMode::kSScaled, 8},
+ {FormatComponentType::kB, FormatMode::kSScaled, 8},
+ }},
+ {"R8G8B8_UINT",
+ FormatType::kR8G8B8_UINT,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ {FormatComponentType::kG, FormatMode::kUInt, 8},
+ {FormatComponentType::kB, FormatMode::kUInt, 8},
+ }},
+ {"R8G8B8_UNORM",
+ FormatType::kR8G8B8_UNORM,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ {FormatComponentType::kG, FormatMode::kUNorm, 8},
+ {FormatComponentType::kB, FormatMode::kUNorm, 8},
+ }},
+ {"R8G8B8_USCALED",
+ FormatType::kR8G8B8_USCALED,
+ 0U,
+ 3U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ {FormatComponentType::kG, FormatMode::kUScaled, 8},
+ {FormatComponentType::kB, FormatMode::kUScaled, 8},
+ }},
+ {"R8G8_SINT",
+ FormatType::kR8G8_SINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ {FormatComponentType::kG, FormatMode::kSInt, 8},
+ }},
+ {"R8G8_SNORM",
+ FormatType::kR8G8_SNORM,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ {FormatComponentType::kG, FormatMode::kSNorm, 8},
+ }},
+ {"R8G8_SRGB",
+ FormatType::kR8G8_SRGB,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ {FormatComponentType::kG, FormatMode::kSRGB, 8},
+ }},
+ {"R8G8_SSCALED",
+ FormatType::kR8G8_SSCALED,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ {FormatComponentType::kG, FormatMode::kSScaled, 8},
+ }},
+ {"R8G8_UINT",
+ FormatType::kR8G8_UINT,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ {FormatComponentType::kG, FormatMode::kUInt, 8},
+ }},
+ {"R8G8_UNORM",
+ FormatType::kR8G8_UNORM,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ {FormatComponentType::kG, FormatMode::kUNorm, 8},
+ }},
+ {"R8G8_USCALED",
+ FormatType::kR8G8_USCALED,
+ 0U,
+ 2U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ {FormatComponentType::kG, FormatMode::kUScaled, 8},
+ }},
+ {"R8_SINT",
+ FormatType::kR8_SINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSInt, 8},
+ }},
+ {"R8_SNORM",
+ FormatType::kR8_SNORM,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSNorm, 8},
+ }},
+ {"R8_SRGB",
+ FormatType::kR8_SRGB,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSRGB, 8},
+ }},
+ {"R8_SSCALED",
+ FormatType::kR8_SSCALED,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kSScaled, 8},
+ }},
+ {"R8_UINT",
+ FormatType::kR8_UINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUInt, 8},
+ }},
+ {"R8_UNORM",
+ FormatType::kR8_UNORM,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUNorm, 8},
+ }},
+ {"R8_USCALED",
+ FormatType::kR8_USCALED,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kR, FormatMode::kUScaled, 8},
+ }},
+ {"S8_UINT",
+ FormatType::kS8_UINT,
+ 0U,
+ 1U,
+ {
+ {FormatComponentType::kS, FormatMode::kUInt, 8},
+ }},
+ {"X8_D24_UNORM_PACK32",
+ FormatType::kX8_D24_UNORM_PACK32,
+ 32U,
+ 2U,
+ {
+ {FormatComponentType::kX, FormatMode::kUNorm, 8},
+ {FormatComponentType::kD, FormatMode::kUNorm, 24},
+ }},
+ };
+
+ for (const auto& fmt : formats) {
+ FormatParser parser;
+ auto format = parser.Parse(fmt.name);
+
+ ASSERT_TRUE(format != nullptr) << fmt.name;
+ EXPECT_EQ(fmt.type, format->GetFormatType()) << fmt.name;
+ EXPECT_EQ(fmt.pack_size, format->GetPackSize()) << fmt.name;
+
+ auto& comps = format->GetComponents();
+ ASSERT_EQ(fmt.component_count, comps.size());
+
+ for (size_t i = 0; i < fmt.component_count; ++i) {
+ EXPECT_EQ(fmt.components[i].type, comps[i].type) << fmt.name;
+ EXPECT_EQ(fmt.components[i].mode, comps[i].mode) << fmt.name;
+ EXPECT_EQ(fmt.components[i].num_bits, comps[i].num_bits) << fmt.name;
+ }
+ }
+}
+
+TEST_F(FormatParserTest, InvalidFormat) {
+ FormatParser parser;
+ auto format = parser.Parse("BLAH_BLAH_BLAH");
+ EXPECT_TRUE(format == nullptr);
+}
+
+TEST_F(FormatParserTest, EmptyFormat) {
+ FormatParser parser;
+ auto format = parser.Parse("");
+ EXPECT_TRUE(format == nullptr);
+}
+
+TEST_F(FormatParserTest, GlslString) {
+ FormatParser parser;
+ auto format = parser.Parse("float/vec3");
+ ASSERT_TRUE(format != nullptr);
+
+ EXPECT_EQ(FormatType::kR32G32B32_SFLOAT, format->GetFormatType());
+ EXPECT_EQ(static_cast<size_t>(0U), format->GetPackSize());
+
+ auto& comps = format->GetComponents();
+ ASSERT_EQ(3U, comps.size());
+
+ EXPECT_EQ(FormatComponentType::kR, comps[0].type);
+ EXPECT_EQ(FormatMode::kSFloat, comps[0].mode);
+ EXPECT_EQ(32U, comps[0].num_bits);
+ EXPECT_EQ(FormatComponentType::kG, comps[1].type);
+ EXPECT_EQ(FormatMode::kSFloat, comps[1].mode);
+ EXPECT_EQ(32U, comps[1].num_bits);
+ EXPECT_EQ(FormatComponentType::kB, comps[2].type);
+ EXPECT_EQ(FormatMode::kSFloat, comps[2].mode);
+ EXPECT_EQ(32U, comps[2].num_bits);
+}
+
+TEST_F(FormatParserTest, GlslStrings) {
+ struct {
+ const char* name;
+ FormatType type;
+ } strs[] = {
+ {"float/vec4", FormatType::kR32G32B32A32_SFLOAT},
+ {"float/ivec3", FormatType::kR32G32B32_SFLOAT},
+ {"float/dvec2", FormatType::kR32G32_SFLOAT},
+ {"float/uvec2", FormatType::kR32G32_SFLOAT},
+ {"float/float", FormatType::kR32_SFLOAT},
+ {"double/double", FormatType::kR64_SFLOAT},
+ {"half/float", FormatType::kR16_SFLOAT},
+ {"byte/int", FormatType::kR8_SINT},
+ {"ubyte/uint", FormatType::kR8_UINT},
+ {"short/int", FormatType::kR16_SINT},
+ {"ushort/uint", FormatType::kR16_UINT},
+ {"int/int", FormatType::kR32_SINT},
+ {"uint/uint", FormatType::kR32_UINT},
+ };
+
+ for (const auto& str : strs) {
+ FormatParser parser;
+ auto format = parser.Parse(str.name);
+ ASSERT_FALSE(format == nullptr);
+
+ EXPECT_EQ(str.type, format->GetFormatType()) << str.name;
+ }
+}
+
+TEST_F(FormatParserTest, GlslStringInvalid) {
+ struct {
+ const char* name;
+ } strs[] = {
+ {"flot/vec3"},
+ {"float/vec1"},
+ {"float/vec22"},
+ {"float/dvec0"},
+ };
+
+ for (const auto& str : strs) {
+ FormatParser parser;
+ auto format = parser.Parse(str.name);
+ EXPECT_TRUE(format == nullptr);
+ }
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/nodes.cc b/src/vkscript/nodes.cc
new file mode 100644
index 0000000..3386011
--- /dev/null
+++ b/src/vkscript/nodes.cc
@@ -0,0 +1,95 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/nodes.h"
+
+#include <cassert>
+
+namespace amber {
+namespace vkscript {
+
+Node::Node(NodeType type) : node_type_(type) {}
+
+Node::~Node() = default;
+
+IndicesNode* Node::AsIndices() {
+ return static_cast<IndicesNode*>(this);
+}
+
+ShaderNode* Node::AsShader() {
+ return static_cast<ShaderNode*>(this);
+}
+
+RequireNode* Node::AsRequire() {
+ return static_cast<RequireNode*>(this);
+}
+
+TestNode* Node::AsTest() {
+ return static_cast<TestNode*>(this);
+}
+
+VertexDataNode* Node::AsVertexData() {
+ return static_cast<VertexDataNode*>(this);
+}
+
+ShaderNode::ShaderNode(ShaderType type, std::vector<uint32_t> shader)
+ : Node(NodeType::kShader), type_(type), shader_(std::move(shader)) {}
+
+ShaderNode::~ShaderNode() = default;
+
+RequireNode::RequireNode() : Node(NodeType::kRequire) {}
+
+RequireNode::~RequireNode() = default;
+
+RequireNode::Requirement::Requirement(Feature feature) : feature_(feature) {}
+
+RequireNode::Requirement::Requirement(Feature feature,
+ std::unique_ptr<Format> format)
+ : feature_(feature), format_(std::move(format)) {}
+
+RequireNode::Requirement::Requirement(Requirement&&) = default;
+
+RequireNode::Requirement::~Requirement() = default;
+
+void RequireNode::AddRequirement(Feature feature) {
+ requirements_.emplace_back(feature);
+}
+
+void RequireNode::AddRequirement(Feature feature,
+ std::unique_ptr<Format> format) {
+ requirements_.emplace_back(feature, std::move(format));
+}
+
+IndicesNode::IndicesNode(const std::vector<uint16_t>& indices)
+ : Node(NodeType::kIndices), indices_(indices) {}
+
+IndicesNode::~IndicesNode() = default;
+
+TestNode::TestNode(std::vector<std::unique_ptr<Command>> cmds)
+ : Node(NodeType::kTest), commands_(std::move(cmds)) {}
+
+TestNode::~TestNode() = default;
+
+VertexDataNode::VertexDataNode() : Node(NodeType::kVertexData) {}
+
+VertexDataNode::~VertexDataNode() = default;
+
+VertexDataNode::Cell::Cell() = default;
+
+VertexDataNode::Cell::Cell(const VertexDataNode::Cell&) = default;
+
+VertexDataNode::Cell::~Cell() = default;
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/nodes.h b/src/vkscript/nodes.h
new file mode 100644
index 0000000..f75f7c0
--- /dev/null
+++ b/src/vkscript/nodes.h
@@ -0,0 +1,171 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_NODES_H_
+#define SRC_VKSCRIPT_NODES_H_
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "src/command.h"
+#include "src/feature.h"
+#include "src/format.h"
+#include "src/tokenizer.h"
+#include "src/vkscript/section_parser.h"
+
+namespace amber {
+namespace vkscript {
+
+class IndicesNode;
+class RequireNode;
+class ShaderNode;
+class TestNode;
+class VertexDataNode;
+
+class Node {
+ public:
+ virtual ~Node();
+
+ bool IsIndices() const { return node_type_ == NodeType::kIndices; }
+ bool IsRequire() const { return node_type_ == NodeType::kRequire; }
+ bool IsShader() const { return node_type_ == NodeType::kShader; }
+ bool IsTest() const { return node_type_ == NodeType::kTest; }
+ bool IsVertexData() const { return node_type_ == NodeType::kVertexData; }
+
+ IndicesNode* AsIndices();
+ RequireNode* AsRequire();
+ ShaderNode* AsShader();
+ TestNode* AsTest();
+ VertexDataNode* AsVertexData();
+
+ protected:
+ Node(NodeType type);
+
+ private:
+ NodeType node_type_;
+};
+
+class ShaderNode : public Node {
+ public:
+ ShaderNode(ShaderType type, std::vector<uint32_t> shader);
+ ~ShaderNode() override;
+
+ ShaderType GetShaderType() const { return type_; }
+ const std::vector<uint32_t>& GetData() const { return shader_; }
+
+ private:
+ ShaderType type_;
+ std::vector<uint32_t> shader_;
+};
+
+class RequireNode : public Node {
+ public:
+ class Requirement {
+ public:
+ Requirement(Feature feature);
+ Requirement(Feature feature, std::unique_ptr<Format> format);
+ Requirement(Requirement&&);
+ ~Requirement();
+
+ Feature GetFeature() const { return feature_; }
+ const Format* GetFormat() const { return format_.get(); }
+
+ private:
+ Feature feature_;
+ std::unique_ptr<Format> format_;
+ };
+
+ RequireNode();
+ ~RequireNode() override;
+
+ void AddRequirement(Feature feature);
+ void AddRequirement(Feature feature, std::unique_ptr<Format> format);
+
+ const std::vector<Requirement>& Requirements() const { return requirements_; }
+
+ void AddExtension(const std::string& ext) { extensions_.push_back(ext); }
+ const std::vector<std::string>& Extensions() const { return extensions_; }
+
+ private:
+ std::vector<Requirement> requirements_;
+ std::vector<std::string> extensions_;
+};
+
+class IndicesNode : public Node {
+ public:
+ IndicesNode(const std::vector<uint16_t>& indices);
+ ~IndicesNode() override;
+
+ const std::vector<uint16_t>& Indices() const { return indices_; }
+
+ private:
+ std::vector<uint16_t> indices_;
+};
+
+class VertexDataNode : public Node {
+ public:
+ struct Header {
+ uint8_t location;
+ std::unique_ptr<Format> format;
+ };
+
+ class Cell {
+ public:
+ Cell();
+ Cell(const Cell&);
+ ~Cell();
+
+ size_t size() const { return data_.size(); }
+ void AppendValue(Value&& v) { data_.emplace_back(std::move(v)); }
+ const Value& GetValue(size_t idx) const { return data_[idx]; }
+
+ private:
+ std::vector<Value> data_;
+ };
+
+ VertexDataNode();
+ ~VertexDataNode() override;
+
+ const std::vector<Header>& GetHeaders() const { return headers_; }
+ void SetHeaders(std::vector<Header> headers) {
+ headers_ = std::move(headers);
+ }
+
+ void AddRow(std::vector<Cell> row) { rows_.push_back(std::move(row)); }
+
+ const std::vector<std::vector<Cell>>& GetRows() const { return rows_; }
+
+ private:
+ std::vector<Header> headers_;
+ std::vector<std::vector<Cell>> rows_;
+};
+
+class TestNode : public Node {
+ public:
+ TestNode(std::vector<std::unique_ptr<Command>> cmds);
+ ~TestNode() override;
+
+ const std::vector<std::unique_ptr<Command>>& GetCommands() const {
+ return commands_;
+ }
+
+ private:
+ std::vector<std::unique_ptr<Command>> commands_;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_NODES_H_
diff --git a/src/vkscript/parser.cc b/src/vkscript/parser.cc
new file mode 100644
index 0000000..ddbf5c4
--- /dev/null
+++ b/src/vkscript/parser.cc
@@ -0,0 +1,395 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/parser.h"
+
+#include <algorithm>
+#include <cassert>
+#include <limits>
+
+#include "src/shader_compiler.h"
+#include "src/tokenizer.h"
+#include "src/vkscript/command_parser.h"
+#include "src/vkscript/format_parser.h"
+#include "src/vkscript/nodes.h"
+
+namespace amber {
+namespace vkscript {
+namespace {
+
+Feature NameToFeature(const std::string& name) {
+ if (name == "robustBufferAccess")
+ return Feature::kRobustBufferAccess;
+ if (name == "fullDrawIndexUint32")
+ return Feature::kFullDrawIndexUint32;
+ if (name == "imageCubeArray")
+ return Feature::kImageCubeArray;
+ if (name == "independentBlend")
+ return Feature::kIndependentBlend;
+ if (name == "geometryShader")
+ return Feature::kGeometryShader;
+ if (name == "tessellationShader")
+ return Feature::kTessellationShader;
+ if (name == "sampleRateShading")
+ return Feature::kSampleRateShading;
+ if (name == "dualSrcBlend")
+ return Feature::kDualSrcBlend;
+ if (name == "logicOp")
+ return Feature::kLogicOp;
+ if (name == "multiDrawIndirect")
+ return Feature::kMultiDrawIndirect;
+ if (name == "drawIndirectFirstInstance")
+ return Feature::kDrawIndirectFirstInstance;
+ if (name == "depthClamp")
+ return Feature::kDepthClamp;
+ if (name == "depthBiasClamp")
+ return Feature::kDepthBiasClamp;
+ if (name == "fillModeNonSolid")
+ return Feature::kFillModeNonSolid;
+ if (name == "depthBounds")
+ return Feature::kDepthBounds;
+ if (name == "wideLines")
+ return Feature::kWideLines;
+ if (name == "largePoints")
+ return Feature::kLargePoints;
+ if (name == "alphaToOne")
+ return Feature::kAlphaToOne;
+ if (name == "multiViewport")
+ return Feature::kMultiViewport;
+ if (name == "samplerAnisotropy")
+ return Feature::kSamplerAnisotropy;
+ if (name == "textureCompressionETC2")
+ return Feature::kTextureCompressionETC2;
+ if (name == "textureCompressionASTC_LDR")
+ return Feature::kTextureCompressionASTC_LDR;
+ if (name == "textureCompressionBC")
+ return Feature::kTextureCompressionBC;
+ if (name == "occlusionQueryPrecise")
+ return Feature::kOcclusionQueryPrecise;
+ if (name == "pipelineStatisticsQuery")
+ return Feature::kPipelineStatisticsQuery;
+ if (name == "vertexPipelineStoresAndAtomics")
+ return Feature::kVertexPipelineStoresAndAtomics;
+ if (name == "fragmentStoresAndAtomics")
+ return Feature::kFragmentStoresAndAtomics;
+ if (name == "shaderTessellationAndGeometryPointSize")
+ return Feature::kShaderTessellationAndGeometryPointSize;
+ if (name == "shaderImageGatherExtended")
+ return Feature::kShaderImageGatherExtended;
+ if (name == "shaderStorageImageExtendedFormats")
+ return Feature::kShaderStorageImageExtendedFormats;
+ if (name == "shaderStorageImageMultisample")
+ return Feature::kShaderStorageImageMultisample;
+ if (name == "shaderStorageImageReadWithoutFormat")
+ return Feature::kShaderStorageImageReadWithoutFormat;
+ if (name == "shaderStorageImageWriteWithoutFormat")
+ return Feature::kShaderStorageImageWriteWithoutFormat;
+ if (name == "shaderUniformBufferArrayDynamicIndexing")
+ return Feature::kShaderUniformBufferArrayDynamicIndexing;
+ if (name == "shaderSampledImageArrayDynamicIndexing")
+ return Feature::kShaderSampledImageArrayDynamicIndexing;
+ if (name == "shaderStorageBufferArrayDynamicIndexing")
+ return Feature::kShaderStorageBufferArrayDynamicIndexing;
+ if (name == "shaderStorageImageArrayDynamicIndexing")
+ return Feature::kShaderStorageImageArrayDynamicIndexing;
+ if (name == "shaderClipDistance")
+ return Feature::kShaderClipDistance;
+ if (name == "shaderCullDistance")
+ return Feature::kShaderCullDistance;
+ if (name == "shaderFloat64")
+ return Feature::kShaderFloat64;
+ if (name == "shaderInt64")
+ return Feature::kShaderInt64;
+ if (name == "shaderInt16")
+ return Feature::kShaderInt16;
+ if (name == "shaderResourceResidency")
+ return Feature::kShaderResourceResidency;
+ if (name == "shaderResourceMinLod")
+ return Feature::kShaderResourceMinLod;
+ if (name == "sparseBinding")
+ return Feature::kSparseBinding;
+ if (name == "sparseResidencyBuffer")
+ return Feature::kSparseResidencyBuffer;
+ if (name == "sparseResidencyImage2D")
+ return Feature::kSparseResidencyImage2D;
+ if (name == "sparseResidencyImage3D")
+ return Feature::kSparseResidencyImage3D;
+ if (name == "sparseResidency2Samples")
+ return Feature::kSparseResidency2Samples;
+ if (name == "sparseResidency4Samples")
+ return Feature::kSparseResidency4Samples;
+ if (name == "sparseResidency8Samples")
+ return Feature::kSparseResidency8Samples;
+ if (name == "sparseResidency16Samples")
+ return Feature::kSparseResidency16Samples;
+ if (name == "sparseResidencyAliased")
+ return Feature::kSparseResidencyAliased;
+ if (name == "variableMultisampleRate")
+ return Feature::kVariableMultisampleRate;
+ if (name == "inheritedQueries")
+ return Feature::kInheritedQueries;
+ if (name == "framebuffer")
+ return Feature::kFramebuffer;
+ if (name == "depthstencil")
+ return Feature::kDepthStencil;
+ return Feature::kUnknown;
+}
+
+} // namespace
+
+Parser::Parser() : amber::Parser() {}
+
+Parser::~Parser() = default;
+
+Result Parser::Parse(const std::string& input) {
+ SectionParser section_parser;
+ Result r = section_parser.Parse(input);
+ if (!r.IsSuccess())
+ return r;
+
+ for (const auto& section : section_parser.Sections()) {
+ r = ProcessSection(section);
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ return {};
+}
+
+Result Parser::ProcessSection(const SectionParser::Section& section) {
+ // Should never get here, but skip it anyway.
+ if (section.section_type == NodeType::kComment)
+ return {};
+
+ if (SectionParser::HasShader(section.section_type))
+ return ProcessShaderBlock(section);
+ if (section.section_type == NodeType::kRequire)
+ return ProcessRequireBlock(section.contents);
+ if (section.section_type == NodeType::kIndices)
+ return ProcessIndicesBlock(section.contents);
+ if (section.section_type == NodeType::kVertexData)
+ return ProcessVertexDataBlock(section.contents);
+ if (section.section_type == NodeType::kTest)
+ return ProcessTestBlock(section.contents);
+
+ return Result("Unknown node type ....");
+}
+
+Result Parser::ProcessShaderBlock(const SectionParser::Section& section) {
+ ShaderCompiler sc;
+
+ assert(SectionParser::HasShader(section.section_type));
+
+ Result r;
+ std::vector<uint32_t> shader;
+ std::tie(r, shader) =
+ sc.Compile(section.shader_type, section.format, section.contents);
+ if (!r.IsSuccess())
+ return r;
+
+ script_.AddShader(section.shader_type, std::move(shader));
+
+ return {};
+}
+
+Result Parser::ProcessRequireBlock(const std::string& data) {
+ auto node = MakeUnique<RequireNode>();
+
+ Tokenizer tokenizer(data);
+ for (auto token = tokenizer.NextToken(); !token->IsEOS();
+ token = tokenizer.NextToken()) {
+ if (token->IsEOL())
+ continue;
+
+ if (!token->IsString())
+ return Result("Failed to parse requirements block.");
+
+ std::string str = token->AsString();
+ Feature feature = NameToFeature(str);
+ if (feature == Feature::kUnknown) {
+ auto it = std::find_if(str.begin(), str.end(),
+ [](char c) { return !(isalnum(c) || c == '_'); });
+ if (it != str.end())
+ return Result("Unknown feature or extension: " + str);
+
+ node->AddExtension(str);
+ } else if (feature == Feature::kFramebuffer) {
+ token = tokenizer.NextToken();
+ if (!token->IsString())
+ return Result("Missing framebuffer format");
+
+ FormatParser fmt_parser;
+ auto fmt = fmt_parser.Parse(token->AsString());
+ if (fmt == nullptr)
+ return Result("Failed to parse framebuffer format");
+
+ node->AddRequirement(feature, std::move(fmt));
+ } else if (feature == Feature::kDepthStencil) {
+ token = tokenizer.NextToken();
+ if (!token->IsString())
+ return Result("Missing depthStencil format");
+
+ FormatParser fmt_parser;
+ auto fmt = fmt_parser.Parse(token->AsString());
+ if (fmt == nullptr)
+ return Result("Failed to parse depthstencil format");
+
+ node->AddRequirement(feature, std::move(fmt));
+ } else {
+ node->AddRequirement(feature);
+ }
+
+ token = tokenizer.NextToken();
+ if (!token->IsEOS() && !token->IsEOL())
+ return Result("Failed to parser requirements block: invalid token");
+ }
+
+ if (!node->Requirements().empty() || !node->Extensions().empty())
+ script_.AddRequireNode(std::move(node));
+
+ return {};
+}
+
+Result Parser::ProcessIndicesBlock(const std::string& data) {
+ std::vector<uint16_t> indices;
+
+ Tokenizer tokenizer(data);
+ for (auto token = tokenizer.NextToken(); !token->IsEOS();
+ token = tokenizer.NextToken()) {
+ if (token->IsEOL())
+ continue;
+
+ if (!token->IsInteger())
+ return Result("Invalid value in indices block");
+ if (token->AsUint64() >
+ static_cast<uint64_t>(std::numeric_limits<uint16_t>::max())) {
+ return Result("Value too large in indices block");
+ }
+
+ indices.push_back(token->AsUint16());
+ }
+
+ if (!indices.empty())
+ script_.AddIndices(indices);
+
+ return {};
+}
+
+Result Parser::ProcessVertexDataBlock(const std::string& data) {
+ Tokenizer tokenizer(data);
+
+ // Skip blank and comment lines
+ auto token = tokenizer.NextToken();
+ while (token->IsEOL())
+ token = tokenizer.NextToken();
+
+ // Skip empty vertex data blocks
+ if (token->IsEOS())
+ return {};
+
+ // Process the header line.
+ std::vector<VertexDataNode::Header> headers;
+ while (!token->IsEOL() && !token->IsEOS()) {
+ // Because of the way the tokenizer works we'll see a number then a string
+ // the string will start with a slash which we have to remove.
+ if (!token->IsInteger())
+ return Result("Unable to process vertex data header");
+
+ uint8_t loc = token->AsUint8();
+
+ token = tokenizer.NextToken();
+ if (!token->IsString())
+ return Result("Unable to process vertex data header");
+
+ std::string fmt_name = token->AsString();
+ if (fmt_name.size() < 2)
+ return Result("Vertex data format too short");
+
+ FormatParser parser;
+ auto fmt = parser.Parse(fmt_name.substr(1, fmt_name.length()));
+ if (!fmt)
+ return Result("Invalid format in vertex data header");
+
+ headers.push_back({loc, std::move(fmt)});
+
+ token = tokenizer.NextToken();
+ }
+
+ auto node = MakeUnique<VertexDataNode>();
+
+ // Process data lines
+ for (; !token->IsEOS(); token = tokenizer.NextToken()) {
+ if (token->IsEOL())
+ continue;
+
+ std::vector<VertexDataNode::Cell> row;
+ for (const auto& header : headers) {
+ row.emplace_back();
+
+ if (header.format->GetPackSize() > 0) {
+ if (!token->IsHex())
+ return Result("Invalid packed value in Vertex Data");
+
+ Value v;
+ v.SetIntValue(token->AsHex());
+ row.back().AppendValue(std::move(v));
+ } else {
+ auto& comps = header.format->GetComponents();
+ for (size_t i = 0; i < comps.size();
+ ++i, token = tokenizer.NextToken()) {
+ if (token->IsEOS() || token->IsEOL())
+ return Result("Too few cells in given vertex data row");
+
+ auto& comp = comps[i];
+
+ Value v;
+ if (comp.mode == FormatMode::kUFloat ||
+ comp.mode == FormatMode::kSFloat) {
+ Result r = token->ConvertToDouble();
+ if (!r.IsSuccess())
+ return r;
+
+ v.SetDoubleValue(token->AsDouble());
+ } else if (token->IsInteger()) {
+ v.SetIntValue(token->AsUint64());
+ } else {
+ return Result("Invalid vertex data value");
+ }
+
+ row.back().AppendValue(std::move(v));
+ }
+ }
+ }
+ node->AddRow(std::move(row));
+ }
+
+ node->SetHeaders(std::move(headers));
+ script_.AddVertexData(std::move(node));
+
+ return {};
+}
+
+Result Parser::ProcessTestBlock(const std::string& data) {
+ CommandParser cp;
+ Result r = cp.Parse(data);
+ if (!r.IsSuccess())
+ return r;
+
+ script_.SetTestCommands(cp.TakeCommands());
+
+ return {};
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/parser.h b/src/vkscript/parser.h
new file mode 100644
index 0000000..efa56af
--- /dev/null
+++ b/src/vkscript/parser.h
@@ -0,0 +1,64 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_PARSER_H_
+#define SRC_VKSCRIPT_PARSER_H_
+
+#include <string>
+
+#include "amber/result.h"
+#include "src/parser.h"
+#include "src/vkscript/script.h"
+#include "src/vkscript/section_parser.h"
+
+namespace amber {
+namespace vkscript {
+
+class Parser : public amber::Parser {
+ public:
+ Parser();
+ ~Parser() override;
+
+ // amber::Parser
+ Result Parse(const std::string& data) override;
+ const amber::Script* GetScript() const override { return &script_; }
+
+ Result ProcessRequireBlockForTesting(const std::string& block) {
+ return ProcessRequireBlock(block);
+ }
+ Result ProcessIndicesBlockForTesting(const std::string& block) {
+ return ProcessIndicesBlock(block);
+ }
+ Result ProcessVertexDataBlockForTesting(const std::string& block) {
+ return ProcessVertexDataBlock(block);
+ }
+ Result ProcessTestBlockForTesting(const std::string& block) {
+ return ProcessTestBlock(block);
+ }
+
+ private:
+ Result ProcessSection(const SectionParser::Section& section);
+ Result ProcessShaderBlock(const SectionParser::Section& section);
+ Result ProcessRequireBlock(const std::string&);
+ Result ProcessIndicesBlock(const std::string&);
+ Result ProcessVertexDataBlock(const std::string&);
+ Result ProcessTestBlock(const std::string&);
+
+ vkscript::Script script_;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_PARSER_H_
diff --git a/src/vkscript/parser_test.cc b/src/vkscript/parser_test.cc
new file mode 100644
index 0000000..ffef56e
--- /dev/null
+++ b/src/vkscript/parser_test.cc
@@ -0,0 +1,548 @@
+// Copyright 2018 The Amber 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 parseried.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "src/vkscript/parser.h"
+#include "gtest/gtest.h"
+#include "src/feature.h"
+#include "src/format.h"
+#include "src/vkscript/nodes.h"
+
+namespace amber {
+namespace vkscript {
+
+using VkScriptParserTest = testing::Test;
+
+TEST_F(VkScriptParserTest, EmptyRequireBlock) {
+ std::string block = "";
+
+ Parser parser;
+ Result r = parser.ProcessRequireBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto script = parser.GetScript();
+ ASSERT_TRUE(script->IsVkScript());
+ EXPECT_TRUE(ToVkScript(script)->Nodes().empty());
+}
+
+TEST_F(VkScriptParserTest, RequireBlockNoArgumentFeatures) {
+ struct {
+ const char* name;
+ Feature feature;
+ } features[] = {
+ {"robustBufferAccess", Feature::kRobustBufferAccess},
+ {"fullDrawIndexUint32", Feature::kFullDrawIndexUint32},
+ {"imageCubeArray", Feature::kImageCubeArray},
+ {"independentBlend", Feature::kIndependentBlend},
+ {"geometryShader", Feature::kGeometryShader},
+ {"tessellationShader", Feature::kTessellationShader},
+ {"sampleRateShading", Feature::kSampleRateShading},
+ {"dualSrcBlend", Feature::kDualSrcBlend},
+ {"logicOp", Feature::kLogicOp},
+ {"multiDrawIndirect", Feature::kMultiDrawIndirect},
+ {"drawIndirectFirstInstance", Feature::kDrawIndirectFirstInstance},
+ {"depthClamp", Feature::kDepthClamp},
+ {"depthBiasClamp", Feature::kDepthBiasClamp},
+ {"fillModeNonSolid", Feature::kFillModeNonSolid},
+ {"depthBounds", Feature::kDepthBounds},
+ {"wideLines", Feature::kWideLines},
+ {"largePoints", Feature::kLargePoints},
+ {"alphaToOne", Feature::kAlphaToOne},
+ {"multiViewport", Feature::kMultiViewport},
+ {"samplerAnisotropy", Feature::kSamplerAnisotropy},
+ {"textureCompressionETC2", Feature::kTextureCompressionETC2},
+ {"textureCompressionASTC_LDR", Feature::kTextureCompressionASTC_LDR},
+ {"textureCompressionBC", Feature::kTextureCompressionBC},
+ {"occlusionQueryPrecise", Feature::kOcclusionQueryPrecise},
+ {"pipelineStatisticsQuery", Feature::kPipelineStatisticsQuery},
+ {"vertexPipelineStoresAndAtomics",
+ Feature::kVertexPipelineStoresAndAtomics},
+ {"fragmentStoresAndAtomics", Feature::kFragmentStoresAndAtomics},
+ {"shaderTessellationAndGeometryPointSize",
+ Feature::kShaderTessellationAndGeometryPointSize},
+ {"shaderImageGatherExtended", Feature::kShaderImageGatherExtended},
+ {"shaderStorageImageExtendedFormats",
+ Feature::kShaderStorageImageExtendedFormats},
+ {"shaderStorageImageMultisample",
+ Feature::kShaderStorageImageMultisample},
+ {"shaderStorageImageReadWithoutFormat",
+ Feature::kShaderStorageImageReadWithoutFormat},
+ {"shaderStorageImageWriteWithoutFormat",
+ Feature::kShaderStorageImageWriteWithoutFormat},
+ {"shaderUniformBufferArrayDynamicIndexing",
+ Feature::kShaderUniformBufferArrayDynamicIndexing},
+ {"shaderSampledImageArrayDynamicIndexing",
+ Feature::kShaderSampledImageArrayDynamicIndexing},
+ {"shaderStorageBufferArrayDynamicIndexing",
+ Feature::kShaderStorageBufferArrayDynamicIndexing},
+ {"shaderStorageImageArrayDynamicIndexing",
+ Feature::kShaderStorageImageArrayDynamicIndexing},
+ {"shaderClipDistance", Feature::kShaderClipDistance},
+ {"shaderCullDistance", Feature::kShaderCullDistance},
+ {"shaderFloat64", Feature::kShaderFloat64},
+ {"shaderInt64", Feature::kShaderInt64},
+ {"shaderInt16", Feature::kShaderInt16},
+ {"shaderResourceResidency", Feature::kShaderResourceResidency},
+ {"shaderResourceMinLod", Feature::kShaderResourceMinLod},
+ {"sparseBinding", Feature::kSparseBinding},
+ {"sparseResidencyBuffer", Feature::kSparseResidencyBuffer},
+ {"sparseResidencyImage2D", Feature::kSparseResidencyImage2D},
+ {"sparseResidencyImage3D", Feature::kSparseResidencyImage3D},
+ {"sparseResidency2Samples", Feature::kSparseResidency2Samples},
+ {"sparseResidency4Samples", Feature::kSparseResidency4Samples},
+ {"sparseResidency8Samples", Feature::kSparseResidency8Samples},
+ {"sparseResidency16Samples", Feature::kSparseResidency16Samples},
+ {"sparseResidencyAliased", Feature::kSparseResidencyAliased},
+ {"variableMultisampleRate", Feature::kVariableMultisampleRate},
+ {"inheritedQueries", Feature::kInheritedQueries},
+ };
+
+ for (const auto& feature : features) {
+ Parser parser;
+ Result r = parser.ProcessRequireBlockForTesting(feature.name);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsRequire());
+
+ auto req = nodes[0]->AsRequire();
+ ASSERT_EQ(1U, req->Requirements().size());
+ EXPECT_EQ(feature.feature, req->Requirements()[0].GetFeature());
+ }
+}
+
+TEST_F(VkScriptParserTest, RequireBlockExtensions) {
+ std::string block = R"(VK_KHR_storage_buffer_storage_class
+VK_KHR_variable_pointers)";
+
+ Parser parser;
+ Result r = parser.ProcessRequireBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsRequire());
+
+ auto req = nodes[0]->AsRequire();
+ ASSERT_EQ(2U, req->Extensions().size());
+
+ const auto& exts = req->Extensions();
+ EXPECT_EQ("VK_KHR_storage_buffer_storage_class", exts[0]);
+ EXPECT_EQ("VK_KHR_variable_pointers", exts[1]);
+}
+
+TEST_F(VkScriptParserTest, RequireBlockFramebuffer) {
+ std::string block = "framebuffer R32G32B32A32_SFLOAT";
+
+ Parser parser;
+ Result r = parser.ProcessRequireBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto script = ToVkScript(parser.GetScript());
+ EXPECT_EQ(1U, script->Nodes().size());
+
+ auto& nodes = script->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsRequire());
+
+ auto* req = nodes[0]->AsRequire();
+ ASSERT_EQ(1U, req->Requirements().size());
+ EXPECT_EQ(Feature::kFramebuffer, req->Requirements()[0].GetFeature());
+
+ auto format = req->Requirements()[0].GetFormat();
+ EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT, format->GetFormatType());
+}
+
+TEST_F(VkScriptParserTest, RequireBlockDepthStencil) {
+ std::string block = "depthstencil D24_UNORM_S8_UINT";
+
+ Parser parser;
+ Result r = parser.ProcessRequireBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToVkScript(parser.GetScript());
+ EXPECT_EQ(1U, script->Nodes().size());
+
+ auto& nodes = script->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsRequire());
+
+ auto* req = nodes[0]->AsRequire();
+ ASSERT_EQ(1U, req->Requirements().size());
+ EXPECT_EQ(Feature::kDepthStencil, req->Requirements()[0].GetFeature());
+
+ auto format = req->Requirements()[0].GetFormat();
+ EXPECT_EQ(FormatType::kD24_UNORM_S8_UINT, format->GetFormatType());
+}
+
+TEST_F(VkScriptParserTest, RequireBlockMultipleLines) {
+ std::string block = R"(
+# Requirements block stuff.
+depthstencil D24_UNORM_S8_UINT
+sparseResidency4Samples
+framebuffer R32G32B32A32_SFLOAT
+# More comments
+inheritedQueries # line comment
+)";
+
+ Parser parser;
+ Result r = parser.ProcessRequireBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto script = ToVkScript(parser.GetScript());
+ EXPECT_EQ(1U, script->Nodes().size());
+
+ auto& nodes = script->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsRequire());
+
+ auto* req = nodes[0]->AsRequire();
+ ASSERT_EQ(4U, req->Requirements().size());
+ EXPECT_EQ(Feature::kDepthStencil, req->Requirements()[0].GetFeature());
+ auto format = req->Requirements()[0].GetFormat();
+ EXPECT_EQ(FormatType::kD24_UNORM_S8_UINT, format->GetFormatType());
+
+ EXPECT_EQ(Feature::kSparseResidency4Samples,
+ req->Requirements()[1].GetFeature());
+
+ EXPECT_EQ(Feature::kFramebuffer, req->Requirements()[2].GetFeature());
+ format = req->Requirements()[2].GetFormat();
+ EXPECT_EQ(FormatType::kR32G32B32A32_SFLOAT, format->GetFormatType());
+
+ EXPECT_EQ(Feature::kInheritedQueries, req->Requirements()[3].GetFeature());
+}
+
+TEST_F(VkScriptParserTest, IndicesBlock) {
+ std::string block = "1 2 3";
+
+ Parser parser;
+ Result r = parser.ProcessIndicesBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsIndices());
+
+ auto& indices = nodes[0]->AsIndices()->Indices();
+ ASSERT_EQ(3U, indices.size());
+
+ EXPECT_EQ(1, indices[0]);
+ EXPECT_EQ(2, indices[1]);
+ EXPECT_EQ(3, indices[2]);
+}
+
+TEST_F(VkScriptParserTest, IndicesBlockMultipleLines) {
+ std::string block = R"(
+# comment line
+1 2 3 4 5 6
+# another comment
+7 8 9 10 11 12
+)";
+
+ std::vector<uint16_t> results = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
+
+ Parser parser;
+ Result r = parser.ProcessIndicesBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsIndices());
+
+ auto& indices = nodes[0]->AsIndices()->Indices();
+ ASSERT_EQ(results.size(), indices.size());
+ for (size_t i = 0; i < results.size(); ++i) {
+ EXPECT_EQ(results[i], indices[i]);
+ }
+}
+
+TEST_F(VkScriptParserTest, IndicesBlockBadValue) {
+ std::string block = "1 a 3";
+
+ Parser parser;
+ Result r = parser.ProcessIndicesBlockForTesting(block);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid value in indices block", r.Error());
+}
+
+TEST_F(VkScriptParserTest, IndicesBlockValueTooLarge) {
+ std::string block = "100000000000 3";
+
+ Parser parser;
+ Result r = parser.ProcessIndicesBlockForTesting(block);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Value too large in indices block", r.Error());
+}
+
+TEST_F(VkScriptParserTest, VertexDataEmpty) {
+ std::string block = "\n#comment\n";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ EXPECT_TRUE(nodes.empty());
+}
+
+TEST_F(VkScriptParserTest, VertexDataHeaderFormatString) {
+ std::string block = "0/R32G32_SFLOAT 1/A8B8G8R8_UNORM_PACK32";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsVertexData());
+
+ auto* data = nodes[0]->AsVertexData();
+ EXPECT_TRUE(data->GetRows().empty());
+
+ auto& headers = data->GetHeaders();
+
+ ASSERT_EQ(2U, headers.size());
+ EXPECT_EQ(static_cast<size_t>(0U), headers[0].location);
+ EXPECT_EQ(FormatType::kR32G32_SFLOAT, headers[0].format->GetFormatType());
+
+ EXPECT_EQ(1U, headers[1].location);
+ EXPECT_EQ(FormatType::kA8B8G8R8_UNORM_PACK32,
+ headers[1].format->GetFormatType());
+}
+
+TEST_F(VkScriptParserTest, VertexDataHeaderGlslString) {
+ std::string block = "0/float/vec2 1/int/vec3";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsVertexData());
+
+ auto* data = nodes[0]->AsVertexData();
+ EXPECT_TRUE(data->GetRows().empty());
+
+ auto& headers = data->GetHeaders();
+
+ ASSERT_EQ(2U, headers.size());
+ EXPECT_EQ(static_cast<size_t>(0U), headers[0].location);
+ EXPECT_EQ(FormatType::kR32G32_SFLOAT, headers[0].format->GetFormatType());
+
+ auto& comps1 = headers[0].format->GetComponents();
+ ASSERT_EQ(2U, comps1.size());
+ EXPECT_EQ(FormatMode::kSFloat, comps1[0].mode);
+ EXPECT_EQ(FormatMode::kSFloat, comps1[1].mode);
+
+ EXPECT_EQ(1U, headers[1].location);
+ EXPECT_EQ(FormatType::kR32G32B32_SINT, headers[1].format->GetFormatType());
+
+ auto& comps2 = headers[1].format->GetComponents();
+ ASSERT_EQ(3U, comps2.size());
+ EXPECT_EQ(FormatMode::kSInt, comps2[0].mode);
+ EXPECT_EQ(FormatMode::kSInt, comps2[1].mode);
+ EXPECT_EQ(FormatMode::kSInt, comps2[2].mode);
+}
+
+TEST_F(VkScriptParserTest, TestBlock) {
+ std::string block = R"(clear color 255 255 255 0
+clear depth 10
+clear stencil 2
+clear)";
+
+ Parser parser;
+ Result r = parser.ProcessTestBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsTest());
+
+ const auto& cmds = nodes[0]->AsTest()->GetCommands();
+ ASSERT_EQ(4U, cmds.size());
+
+ ASSERT_TRUE(cmds[0]->IsClearColor());
+ auto* color_cmd = cmds[0]->AsClearColor();
+ EXPECT_FLOAT_EQ(255.f, color_cmd->GetR());
+ EXPECT_FLOAT_EQ(255.f, color_cmd->GetG());
+ EXPECT_FLOAT_EQ(255.f, color_cmd->GetB());
+ EXPECT_FLOAT_EQ(0.0f, color_cmd->GetA());
+
+ ASSERT_TRUE(cmds[1]->IsClearDepth());
+ EXPECT_EQ(10U, cmds[1]->AsClearDepth()->GetValue());
+
+ ASSERT_TRUE(cmds[2]->IsClearStencil());
+ EXPECT_FLOAT_EQ(2, cmds[2]->AsClearStencil()->GetValue());
+
+ EXPECT_TRUE(cmds[3]->IsClear());
+}
+
+TEST_F(VkScriptParserTest, VertexDataRows) {
+ std::string block = R"(
+# Vertex data
+0/R32G32B32_SFLOAT 1/R8G8B8_UNORM
+-1 -1 0.25 255 0 0 # ending comment
+# Another Row
+0.25 -1 0.25 255 0 255
+)";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsVertexData());
+
+ auto* data = nodes[0]->AsVertexData();
+ auto& headers = data->GetHeaders();
+ ASSERT_EQ(2U, headers.size());
+
+ // Rows is a vector of vector of cells
+ auto& rows = data->GetRows();
+ ASSERT_EQ(2U, rows.size());
+
+ // A row is a vector of Cells
+ const auto& row1 = rows[0];
+ ASSERT_EQ(2U, row1.size());
+
+ const auto& row1_cell1 = row1[0];
+ ASSERT_EQ(3U, row1_cell1.size());
+
+ ASSERT_TRUE(row1_cell1.GetValue(0).IsFloat());
+ EXPECT_FLOAT_EQ(-1, row1_cell1.GetValue(0).AsFloat());
+ ASSERT_TRUE(row1_cell1.GetValue(1).IsFloat());
+ EXPECT_FLOAT_EQ(-1, row1_cell1.GetValue(1).AsFloat());
+ ASSERT_TRUE(row1_cell1.GetValue(2).IsFloat());
+ EXPECT_FLOAT_EQ(0.25, row1_cell1.GetValue(2).AsFloat());
+
+ const auto& row1_cell2 = row1[1];
+ ASSERT_EQ(3U, row1_cell2.size());
+
+ ASSERT_TRUE(row1_cell2.GetValue(0).IsInteger());
+ EXPECT_FLOAT_EQ(255, row1_cell2.GetValue(0).AsUint8());
+ ASSERT_TRUE(row1_cell2.GetValue(1).IsInteger());
+ EXPECT_FLOAT_EQ(0, row1_cell2.GetValue(1).AsUint8());
+ ASSERT_TRUE(row1_cell2.GetValue(2).IsInteger());
+ EXPECT_FLOAT_EQ(0, row1_cell2.GetValue(2).AsUint8());
+
+ // Second row.
+ const auto& row2 = rows[1];
+ ASSERT_EQ(2U, row2.size());
+
+ const auto& row2_cell1 = row2[0];
+ ASSERT_EQ(3U, row2_cell1.size());
+
+ ASSERT_TRUE(row2_cell1.GetValue(0).IsFloat());
+ EXPECT_FLOAT_EQ(0.25, row2_cell1.GetValue(0).AsFloat());
+ ASSERT_TRUE(row2_cell1.GetValue(1).IsFloat());
+ EXPECT_FLOAT_EQ(-1, row2_cell1.GetValue(1).AsFloat());
+ ASSERT_TRUE(row2_cell1.GetValue(2).IsFloat());
+ EXPECT_FLOAT_EQ(0.25, row2_cell1.GetValue(2).AsFloat());
+
+ const auto& row2_cell2 = row2[1];
+ ASSERT_EQ(3U, row2_cell2.size());
+
+ ASSERT_TRUE(row2_cell2.GetValue(0).IsInteger());
+ EXPECT_FLOAT_EQ(255, row2_cell2.GetValue(0).AsUint8());
+ ASSERT_TRUE(row2_cell2.GetValue(1).IsInteger());
+ EXPECT_FLOAT_EQ(0, row2_cell2.GetValue(1).AsUint8());
+ ASSERT_TRUE(row2_cell2.GetValue(2).IsInteger());
+ EXPECT_FLOAT_EQ(255, row2_cell2.GetValue(2).AsUint8());
+}
+
+TEST_F(VkScriptParserTest, VertexDataShortRow) {
+ std::string block = R"(
+0/R32G32B32_SFLOAT 1/R8G8B8_UNORM
+-1 -1 0.25 255 0 0
+0.25 -1 0.25 255 0
+)";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Too few cells in given vertex data row", r.Error());
+}
+
+TEST_F(VkScriptParserTest, VertexDataIncorrectValue) {
+ std::string block = R"(
+0/R32G32B32_SFLOAT 1/R8G8B8_UNORM
+-1 -1 0.25 255 StringValue 0
+0.25 -1 0.25 255 0 0
+)";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid vertex data value", r.Error());
+}
+
+TEST_F(VkScriptParserTest, VertexDataRowsWithHex) {
+ std::string block = R"(
+0/A8B8G8R8_UNORM_PACK32
+0xff0000ff
+0xffff0000
+)";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto& nodes = ToVkScript(parser.GetScript())->Nodes();
+ ASSERT_EQ(1U, nodes.size());
+ ASSERT_TRUE(nodes[0]->IsVertexData());
+
+ auto* data = nodes[0]->AsVertexData();
+ auto& headers = data->GetHeaders();
+ ASSERT_EQ(1U, headers.size());
+
+ auto& rows = data->GetRows();
+ ASSERT_EQ(2U, rows.size());
+
+ // Each row has 1 cell.
+ auto& row1 = rows[0];
+ ASSERT_EQ(1U, row1.size());
+
+ auto& row1_cell1 = row1[0];
+ ASSERT_EQ(1U, row1_cell1.size());
+
+ ASSERT_TRUE(row1_cell1.GetValue(0).IsInteger());
+ EXPECT_EQ(0xff0000ff, row1_cell1.GetValue(0).AsUint32());
+
+ auto& row2 = rows[1];
+ ASSERT_EQ(1U, row1.size());
+
+ const auto& row2_cell1 = row2[0];
+ ASSERT_EQ(1U, row2_cell1.size());
+
+ ASSERT_TRUE(row2_cell1.GetValue(0).IsInteger());
+ EXPECT_EQ(0xffff0000, row2_cell1.GetValue(0).AsUint32());
+}
+
+TEST_F(VkScriptParserTest, VertexDataRowsWithHexWrongColumn) {
+ std::string block = R"(
+0/R32G32B32_SFLOAT 1/R8G8B8_UNORM
+-1 -1 0.25 0xffff0000
+0.25 -1 0.25 255 0
+)";
+
+ Parser parser;
+ Result r = parser.ProcessVertexDataBlockForTesting(block);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid vertex data value", r.Error());
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/script.cc b/src/vkscript/script.cc
new file mode 100644
index 0000000..2c08e5b
--- /dev/null
+++ b/src/vkscript/script.cc
@@ -0,0 +1,55 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/script.h"
+
+#include "src/make_unique.h"
+#include "src/vkscript/nodes.h"
+
+namespace amber {
+
+const vkscript::Script* ToVkScript(const amber::Script* s) {
+ return static_cast<const vkscript::Script*>(s);
+}
+
+namespace vkscript {
+
+Script::Script() : amber::Script(ScriptType::kVkScript) {}
+
+Script::~Script() = default;
+
+void Script::AddRequireNode(std::unique_ptr<RequireNode> node) {
+ std::unique_ptr<Node> tn(node.release());
+ test_nodes_.push_back(std::move(tn));
+}
+
+void Script::AddShader(ShaderType type, std::vector<uint32_t> shader) {
+ test_nodes_.push_back(MakeUnique<ShaderNode>(type, std::move(shader)));
+}
+
+void Script::AddIndices(const std::vector<uint16_t>& indices) {
+ test_nodes_.push_back(MakeUnique<IndicesNode>(indices));
+}
+
+void Script::AddVertexData(std::unique_ptr<VertexDataNode> node) {
+ std::unique_ptr<Node> tn(node.release());
+ test_nodes_.push_back(std::move(tn));
+}
+
+void Script::SetTestCommands(std::vector<std::unique_ptr<Command>> cmds) {
+ test_nodes_.push_back(MakeUnique<TestNode>(std::move(cmds)));
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/script.h b/src/vkscript/script.h
new file mode 100644
index 0000000..966e52a
--- /dev/null
+++ b/src/vkscript/script.h
@@ -0,0 +1,58 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_SCRIPT_H_
+#define SRC_VKSCRIPT_SCRIPT_H_
+
+#include <memory>
+#include <vector>
+
+#include "src/command.h"
+#include "src/make_unique.h"
+#include "src/script.h"
+#include "src/vkscript/section_parser.h"
+
+namespace amber {
+namespace vkscript {
+
+class Node;
+class RequireNode;
+class VertexDataNode;
+
+class Script : public amber::Script {
+ public:
+ Script();
+ ~Script() override;
+
+ void AddRequireNode(std::unique_ptr<RequireNode> node);
+ void AddShader(ShaderType, std::vector<uint32_t>);
+ void AddIndices(const std::vector<uint16_t>& indices);
+ void AddVertexData(std::unique_ptr<VertexDataNode> node);
+ void SetTestCommands(std::vector<std::unique_ptr<Command>> commands);
+
+ const std::vector<std::unique_ptr<Node>>& Nodes() const {
+ return test_nodes_;
+ }
+
+ private:
+ std::vector<std::unique_ptr<Node>> test_nodes_;
+};
+
+} // namespace vkscript
+
+const vkscript::Script* ToVkScript(const amber::Script* s);
+
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_SCRIPT_H_
diff --git a/src/vkscript/section_parser.cc b/src/vkscript/section_parser.cc
new file mode 100644
index 0000000..a76db66
--- /dev/null
+++ b/src/vkscript/section_parser.cc
@@ -0,0 +1,205 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/section_parser.h"
+
+#include <cassert>
+#include <iostream>
+#include <sstream>
+#include <string>
+
+#include "src/make_unique.h"
+
+namespace amber {
+namespace vkscript {
+
+// static
+bool SectionParser::HasShader(const NodeType type) {
+ return type == NodeType::kShader;
+}
+
+SectionParser::SectionParser() = default;
+
+SectionParser::~SectionParser() = default;
+
+Result SectionParser::Parse(const std::string& data) {
+ Result result = SplitSections(data);
+ if (!result.IsSuccess())
+ return result;
+ return {};
+}
+
+Result SectionParser::NameToNodeType(const std::string& data,
+ NodeType* section_type,
+ ShaderType* shader_type,
+ ShaderFormat* fmt) const {
+ assert(section_type);
+ assert(shader_type);
+ assert(fmt);
+
+ *fmt = ShaderFormat::kText;
+
+ std::string name;
+ size_t pos = data.rfind(" spirv hex");
+ if (pos != std::string::npos) {
+ *fmt = ShaderFormat::kSpirvHex;
+ name = data.substr(0, pos);
+ } else {
+ pos = data.rfind(" spirv");
+ if (pos != std::string::npos) {
+ *fmt = ShaderFormat::kSpirvAsm;
+ name = data.substr(0, pos);
+ } else {
+ name = data;
+ }
+ }
+
+ pos = data.rfind(" passthrough");
+ if (pos != std::string::npos) {
+ *fmt = ShaderFormat::kDefault;
+ name = data.substr(0, pos);
+ }
+
+ if (name == "comment") {
+ *section_type = NodeType::kComment;
+ } else if (name == "indices") {
+ *section_type = NodeType::kIndices;
+ } else if (name == "require") {
+ *section_type = NodeType::kRequire;
+ } else if (name == "test") {
+ *section_type = NodeType::kTest;
+ } else if (name == "vertex data") {
+ *section_type = NodeType::kVertexData;
+ } else if (name == "compute shader") {
+ *section_type = NodeType::kShader;
+ *shader_type = ShaderType::kCompute;
+ if (*fmt == ShaderFormat::kText)
+ *fmt = ShaderFormat::kGlsl;
+ } else if (name == "fragment shader") {
+ *section_type = NodeType::kShader;
+ *shader_type = ShaderType::kFragment;
+ if (*fmt == ShaderFormat::kText)
+ *fmt = ShaderFormat::kGlsl;
+ } else if (name == "geometry shader") {
+ *section_type = NodeType::kShader;
+ *shader_type = ShaderType::kGeometry;
+ if (*fmt == ShaderFormat::kText)
+ *fmt = ShaderFormat::kGlsl;
+ } else if (name == "tessellation control shader") {
+ *section_type = NodeType::kShader;
+ *shader_type = ShaderType::kTessellationControl;
+ if (*fmt == ShaderFormat::kText)
+ *fmt = ShaderFormat::kGlsl;
+ } else if (name == "tessellation evaluation shader") {
+ *section_type = NodeType::kShader;
+ *shader_type = ShaderType::kTessellationEvaluation;
+ if (*fmt == ShaderFormat::kText)
+ *fmt = ShaderFormat::kGlsl;
+ } else if (name == "vertex shader") {
+ *section_type = NodeType::kShader;
+ *shader_type = ShaderType::kVertex;
+ if (*fmt == ShaderFormat::kText)
+ *fmt = ShaderFormat::kGlsl;
+ } else {
+ return Result("Invalid name: " + data);
+ }
+
+ if (!SectionParser::HasShader(*section_type) &&
+ (*fmt == ShaderFormat::kGlsl || *fmt == ShaderFormat::kSpirvAsm ||
+ *fmt == ShaderFormat::kSpirvHex)) {
+ return Result("Invalid source format: " + data);
+ }
+
+ return {};
+}
+
+void SectionParser::AddSection(NodeType section_type,
+ ShaderType shader_type,
+ ShaderFormat fmt,
+ const std::string& contents) {
+ if (section_type == NodeType::kComment)
+ return;
+
+ if (fmt == ShaderFormat::kDefault) {
+ sections_.push_back({section_type, shader_type, ShaderFormat::kSpirvAsm,
+ kPassThroughShader});
+ return;
+ }
+
+ size_t size = contents.size();
+ while (size > 0) {
+ if (contents[size - 1] == '\n' || contents[size - 1] == '\r') {
+ --size;
+ continue;
+ }
+ break;
+ }
+
+ sections_.push_back(
+ {section_type, shader_type, fmt, contents.substr(0, size)});
+}
+
+Result SectionParser::SplitSections(const std::string& data) {
+ std::stringstream ss(data);
+ size_t line_count = 0;
+ bool in_section = false;
+
+ NodeType current_type = NodeType::kComment;
+ ShaderType current_shader = ShaderType::kVertex;
+ ShaderFormat current_fmt = ShaderFormat::kText;
+ std::string section_contents;
+
+ for (std::string line; std::getline(ss, line);) {
+ ++line_count;
+
+ if (!in_section) {
+ if (line.empty() || line[0] == '#' || line == "\r")
+ continue;
+
+ if (line[0] != '[')
+ return Result(std::to_string(line_count) + ": Invalid character");
+
+ in_section = true;
+ }
+
+ if (line.empty()) {
+ section_contents += "\n";
+ continue;
+ }
+
+ if (line[0] == '[') {
+ AddSection(current_type, current_shader, current_fmt, section_contents);
+ section_contents = "";
+
+ size_t name_end = line.rfind("]");
+ if (name_end == std::string::npos)
+ return Result(std::to_string(line_count) + ": Missing section close");
+
+ std::string name = line.substr(1, name_end - 1);
+
+ Result r =
+ NameToNodeType(name, &current_type, &current_shader, &current_fmt);
+ if (!r.IsSuccess())
+ return Result(std::to_string(line_count) + ": " + r.Error());
+ } else {
+ section_contents += line + "\n";
+ }
+ }
+ AddSection(current_type, current_shader, current_fmt, section_contents);
+
+ return {};
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vkscript/section_parser.h b/src/vkscript/section_parser.h
new file mode 100644
index 0000000..5eb4ad5
--- /dev/null
+++ b/src/vkscript/section_parser.h
@@ -0,0 +1,82 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VKSCRIPT_SECTION_PARSER_H_
+#define SRC_VKSCRIPT_SECTION_PARSER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/shader_data.h"
+
+namespace amber {
+namespace vkscript {
+
+enum class NodeType : uint8_t {
+ kComment = 0,
+ kShader,
+ kIndices,
+ kVertexData,
+ kRequire,
+ kTest,
+};
+
+class SectionParser {
+ public:
+ struct Section {
+ NodeType section_type;
+ ShaderType shader_type; // Only valid when section_type == kShader
+ ShaderFormat format;
+ std::string contents;
+ };
+
+ static bool HasShader(const NodeType type);
+
+ SectionParser();
+ ~SectionParser();
+
+ Result Parse(const std::string& data);
+ const std::vector<Section>& Sections() { return sections_; }
+
+ Result SplitSectionsForTesting(const std::string& data) {
+ return SplitSections(data);
+ }
+
+ Result NameToNodeTypeForTesting(const std::string& name,
+ NodeType* section_type,
+ ShaderType* shader_type,
+ ShaderFormat* fmt) const {
+ return NameToNodeType(name, section_type, shader_type, fmt);
+ }
+
+ private:
+ Result SplitSections(const std::string& data);
+ void AddSection(NodeType section_type,
+ ShaderType shader_type,
+ ShaderFormat fmt,
+ const std::string& contents);
+ Result NameToNodeType(const std::string& name,
+ NodeType* section_type,
+ ShaderType* shader_type,
+ ShaderFormat* fmt) const;
+
+ std::vector<Section> sections_;
+};
+
+} // namespace vkscript
+} // namespace amber
+
+#endif // SRC_VKSCRIPT_SECTION_PARSER_H_
diff --git a/src/vkscript/section_parser_test.cc b/src/vkscript/section_parser_test.cc
new file mode 100644
index 0000000..664e181
--- /dev/null
+++ b/src/vkscript/section_parser_test.cc
@@ -0,0 +1,299 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vkscript/section_parser.h"
+#include "gtest/gtest.h"
+
+namespace amber {
+namespace vkscript {
+
+using SectionParserTest = testing::Test;
+
+TEST_F(SectionParserTest, SectionParserCommentSection) {
+ std::string input = "[comment]\nThis is the comment body\n.Lots of Text.";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto sections = p.Sections();
+ EXPECT_TRUE(sections.empty());
+}
+
+TEST_F(SectionParserTest, ParseShaderGlslVertex) {
+ std::string shader = R"(#version 430
+void main() {
+})";
+ std::string input = "[vertex shader]\n" + shader;
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto sections = p.Sections();
+ ASSERT_EQ(1U, sections.size());
+ EXPECT_EQ(NodeType::kShader, sections[0].section_type);
+ EXPECT_EQ(ShaderType::kVertex, sections[0].shader_type);
+ EXPECT_EQ(ShaderFormat::kGlsl, sections[0].format);
+ EXPECT_EQ(shader, sections[0].contents);
+}
+
+TEST_F(SectionParserTest, ParseShaderGlslVertexPassthrough) {
+ std::string input = "[vertex shader passthrough]";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto sections = p.Sections();
+ ASSERT_EQ(1U, sections.size());
+ EXPECT_EQ(NodeType::kShader, sections[0].section_type);
+ EXPECT_EQ(ShaderType::kVertex, sections[0].shader_type);
+ EXPECT_EQ(ShaderFormat::kSpirvAsm, sections[0].format);
+ EXPECT_EQ(kPassThroughShader, sections[0].contents);
+}
+
+TEST_F(SectionParserTest, SectionParserMultipleSections) {
+ std::string input = R"(
+[comment]
+This is a test.
+
+[vertex shader passthrough]
+[fragment shader]
+#version 430
+void main() {}
+
+[geometry shader]
+float4 main() {}
+
+[comment]
+Another comment section.
+Multi line.
+
+[indices]
+1 2 3 4
+5 6 7 8
+[test]
+test body.)";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto sections = p.Sections();
+ ASSERT_EQ(5U, sections.size());
+
+ // Passthrough vertext shader
+ EXPECT_EQ(NodeType::kShader, sections[0].section_type);
+ EXPECT_EQ(ShaderType::kVertex, sections[0].shader_type);
+ EXPECT_EQ(ShaderFormat::kSpirvAsm, sections[0].format);
+ EXPECT_EQ(kPassThroughShader, sections[0].contents);
+
+ // fragment shader
+ EXPECT_EQ(NodeType::kShader, sections[1].section_type);
+ EXPECT_EQ(ShaderType::kFragment, sections[1].shader_type);
+ EXPECT_EQ(ShaderFormat::kGlsl, sections[1].format);
+ EXPECT_EQ("#version 430\nvoid main() {}", sections[1].contents);
+
+ // geometry shader
+ EXPECT_EQ(NodeType::kShader, sections[2].section_type);
+ EXPECT_EQ(ShaderType::kGeometry, sections[2].shader_type);
+ EXPECT_EQ(ShaderFormat::kGlsl, sections[2].format);
+ EXPECT_EQ("float4 main() {}", sections[2].contents);
+
+ // indices
+ EXPECT_EQ(NodeType::kIndices, sections[3].section_type);
+ EXPECT_EQ(ShaderFormat::kText, sections[3].format);
+ EXPECT_EQ("1 2 3 4\n5 6 7 8", sections[3].contents);
+
+ // test
+ EXPECT_EQ(NodeType::kTest, sections[4].section_type);
+ EXPECT_EQ(ShaderFormat::kText, sections[4].format);
+ EXPECT_EQ("test body.", sections[4].contents);
+}
+
+TEST_F(SectionParserTest, SkipCommentLinesOutsideSections) {
+ std::string input = "# comment 1\n#comment 2\r\n[vertex shader]";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ ASSERT_TRUE(r.IsSuccess());
+
+ auto sections = p.Sections();
+ ASSERT_EQ(1U, sections.size());
+ EXPECT_EQ(NodeType::kShader, sections[0].section_type);
+ EXPECT_EQ(ShaderType::kVertex, sections[0].shader_type);
+ EXPECT_EQ(ShaderFormat::kGlsl, sections[0].format);
+ EXPECT_EQ("", sections[0].contents);
+}
+
+TEST_F(SectionParserTest, SkipBlankLinesOutsideSections) {
+ std::string input = "\n\r\n[vertex shader]";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+
+ auto sections = p.Sections();
+ ASSERT_EQ(1U, sections.size());
+ EXPECT_EQ(NodeType::kShader, sections[0].section_type);
+ EXPECT_EQ(ShaderType::kVertex, sections[0].shader_type);
+ EXPECT_EQ(ShaderFormat::kGlsl, sections[0].format);
+ EXPECT_EQ("", sections[0].contents);
+}
+
+TEST_F(SectionParserTest, UnknownTextOutsideSection) {
+ std::string input = "Invalid Text";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: Invalid character", r.Error());
+}
+
+TEST_F(SectionParserTest, UnknownSectionName) {
+ std::string input = "[Invalid Section]";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: Invalid name: Invalid Section", r.Error());
+}
+
+TEST_F(SectionParserTest, MissingSectionClose) {
+ std::string input = "[vertex shader\nMore Content";
+
+ SectionParser p;
+ Result r = p.SplitSectionsForTesting(input);
+ EXPECT_FALSE(r.IsSuccess());
+ EXPECT_EQ("1: Missing section close", r.Error());
+}
+
+TEST_F(SectionParserTest, NameToNodeType) {
+ struct {
+ const char* name;
+ NodeType section_type;
+ ShaderType shader_type;
+ ShaderFormat fmt;
+ } name_cases[] = {
+ {"comment", NodeType::kComment, ShaderType::kVertex, ShaderFormat::kText},
+ {"indices", NodeType::kIndices, ShaderType::kVertex, ShaderFormat::kText},
+ {"require", NodeType::kRequire, ShaderType::kVertex, ShaderFormat::kText},
+ {"test", NodeType::kTest, ShaderType::kVertex, ShaderFormat::kText},
+ {"vertex data", NodeType::kVertexData, ShaderType::kVertex,
+ ShaderFormat::kText},
+
+ {"compute shader", NodeType::kShader, ShaderType::kCompute,
+ ShaderFormat::kGlsl},
+ {"fragment shader", NodeType::kShader, ShaderType::kFragment,
+ ShaderFormat::kGlsl},
+ {"geometry shader", NodeType::kShader, ShaderType::kGeometry,
+ ShaderFormat::kGlsl},
+ {"tessellation control shader", NodeType::kShader,
+ ShaderType::kTessellationControl, ShaderFormat::kGlsl},
+ {"tessellation evaluation shader", NodeType::kShader,
+ ShaderType::kTessellationEvaluation, ShaderFormat::kGlsl},
+ {"vertex shader", NodeType::kShader, ShaderType::kVertex,
+ ShaderFormat::kGlsl},
+ {"compute shader spirv", NodeType::kShader, ShaderType::kCompute,
+ ShaderFormat::kSpirvAsm},
+ {"fragment shader spirv", NodeType::kShader, ShaderType::kFragment,
+ ShaderFormat::kSpirvAsm},
+ {"geometry shader spirv", NodeType::kShader, ShaderType::kGeometry,
+ ShaderFormat::kSpirvAsm},
+ {"tessellation control shader spirv", NodeType::kShader,
+ ShaderType::kTessellationControl, ShaderFormat::kSpirvAsm},
+ {"tessellation evaluation shader spirv", NodeType::kShader,
+ ShaderType::kTessellationEvaluation, ShaderFormat::kSpirvAsm},
+ {"vertex shader spirv", NodeType::kShader, ShaderType::kVertex,
+ ShaderFormat::kSpirvAsm},
+ {"compute shader spirv hex", NodeType::kShader, ShaderType::kCompute,
+ ShaderFormat::kSpirvHex},
+ {"fragment shader spirv hex", NodeType::kShader, ShaderType::kFragment,
+ ShaderFormat::kSpirvHex},
+ {"geometry shader spirv hex", NodeType::kShader, ShaderType::kGeometry,
+ ShaderFormat::kSpirvHex},
+ {"tessellation control shader spirv hex", NodeType::kShader,
+ ShaderType::kTessellationControl, ShaderFormat::kSpirvHex},
+ {"tessellation evaluation shader spirv hex", NodeType::kShader,
+ ShaderType::kTessellationEvaluation, ShaderFormat::kSpirvHex},
+ {"vertex shader spirv hex", NodeType::kShader, ShaderType::kVertex,
+ ShaderFormat::kSpirvHex},
+ {"vertex shader passthrough", NodeType::kShader, ShaderType::kVertex,
+ ShaderFormat::kDefault}};
+
+ for (auto name_case : name_cases) {
+ NodeType section_type = NodeType::kTest;
+ ShaderType shader_type = ShaderType::kVertex;
+ ShaderFormat fmt = ShaderFormat::kText;
+ SectionParser p;
+ Result r = p.NameToNodeTypeForTesting(name_case.name, &section_type,
+ &shader_type, &fmt);
+
+ ASSERT_TRUE(r.IsSuccess()) << r.Error();
+ EXPECT_EQ(name_case.section_type, section_type) << name_case.name;
+ EXPECT_EQ(name_case.shader_type, shader_type) << name_case.name;
+ EXPECT_EQ(name_case.fmt, fmt) << name_case.name;
+ }
+}
+
+TEST_F(SectionParserTest, NameToNodeTypeInvalidName) {
+ NodeType section_type = NodeType::kTest;
+ ShaderType shader_type = ShaderType::kVertex;
+ ShaderFormat fmt = ShaderFormat::kText;
+ SectionParser p;
+ Result r = p.NameToNodeTypeForTesting("InvalidName", &section_type,
+ &shader_type, &fmt);
+ ASSERT_FALSE(r.IsSuccess());
+ EXPECT_EQ("Invalid name: InvalidName", r.Error());
+}
+
+TEST_F(SectionParserTest, NameToSectionInvalidSuffix) {
+ struct {
+ const char* name;
+ } cases[] = {{"comment spirv"}, {"indices spirv"},
+ {"require spirv"}, {"test spirv"},
+ {"vertex data spirv"}, {"comment spirv hex"},
+ {"indices spirv hex"}, {"require spirv hex"},
+ {"test spirv hex"}, {"vertex data spirv hex"}};
+
+ for (auto name_case : cases) {
+ NodeType section_type = NodeType::kTest;
+ ShaderType shader_type = ShaderType::kVertex;
+ ShaderFormat fmt = ShaderFormat::kText;
+ SectionParser p;
+
+ Result r = p.NameToNodeTypeForTesting(name_case.name, &section_type,
+ &shader_type, &fmt);
+ ASSERT_FALSE(r.IsSuccess()) << name_case.name;
+ EXPECT_EQ("Invalid source format: " + std::string(name_case.name),
+ r.Error());
+ }
+}
+
+TEST_F(SectionParserTest, HasShader) {
+ EXPECT_TRUE(SectionParser::HasShader(NodeType::kShader));
+}
+
+TEST_F(SectionParserTest, HasNoShader) {
+ const NodeType false_types[] = {NodeType::kComment, NodeType::kTest,
+ NodeType::kIndices, NodeType::kVertexData,
+ NodeType::kRequire};
+ for (auto type : false_types) {
+ EXPECT_FALSE(SectionParser::HasShader(type));
+ }
+}
+
+} // namespace vkscript
+} // namespace amber
diff --git a/src/vulkan/CMakeLists.txt b/src/vulkan/CMakeLists.txt
new file mode 100644
index 0000000..8df5759
--- /dev/null
+++ b/src/vulkan/CMakeLists.txt
@@ -0,0 +1,43 @@
+# Copyright 2018 The Amber 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.
+
+set(VULKAN_ENGINE_SOURCES
+ bit_copy.cc
+ buffer.cc
+ command.cc
+ descriptor.cc
+ device.cc
+ engine_vulkan.cc
+ format_data.cc
+ frame_buffer.cc
+ resource.cc
+ image.cc
+ pipeline.cc
+ graphics_pipeline.cc
+ vertex_buffer.cc
+)
+
+add_library(libamberenginevulkan ${VULKAN_ENGINE_SOURCES})
+amber_default_compile_options(libamberenginevulkan)
+set_target_properties(libamberenginevulkan PROPERTIES
+ OUTPUT_NAME "amberenginevulkan"
+)
+target_link_libraries(libamberenginevulkan ${VULKAN_LIB})
+
+if(${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
+ # vulkan/vulkan.h defines VK_NULL_HANDLE as 0u and that also serves as a null pointer.
+ # Disable Clang's warning that will alwaays fire on that. This is required to build
+ # with XCode 10.
+ target_compile_options(libamberenginevulkan PRIVATE -Wno-zero-as-null-pointer-constant)
+endif()
diff --git a/src/vulkan/bit_copy.cc b/src/vulkan/bit_copy.cc
new file mode 100644
index 0000000..3be826f
--- /dev/null
+++ b/src/vulkan/bit_copy.cc
@@ -0,0 +1,166 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/bit_copy.h"
+
+#include <cassert>
+#include <cstring>
+
+namespace amber {
+namespace vulkan {
+
+// static
+void BitCopy::ShiftBufferBits(uint8_t* buffer,
+ uint8_t length_bytes,
+ uint8_t shift_bits) {
+ if (shift_bits == 0)
+ return;
+
+ assert(shift_bits < 8);
+
+ uint8_t carry = 0;
+ for (uint32_t i = 0; i < length_bytes; ++i) {
+ uint8_t new_value = static_cast<uint8_t>(buffer[i] << shift_bits) | carry;
+ carry = buffer[i] >> (8 - shift_bits);
+ buffer[i] = new_value;
+ }
+}
+
+// static
+void BitCopy::CopyValueToBuffer(uint8_t* dst,
+ const Value& src,
+ uint8_t bit_offset,
+ uint8_t bits) {
+ uint8_t data[9] = {};
+ if (src.IsInteger()) {
+ if (bits <= 8) {
+ uint8_t data_uint8 = src.AsUint8();
+ data[0] = data_uint8;
+ } else if (bits <= 16) {
+ uint16_t data_uint16 = src.AsUint16();
+ std::memcpy(data, &data_uint16, sizeof(uint16_t));
+ } else if (bits <= 32) {
+ uint32_t data_uint32 = src.AsUint32();
+ std::memcpy(data, &data_uint32, sizeof(uint32_t));
+ } else if (bits <= 64) {
+ uint64_t data_uint64 = src.AsUint64();
+ std::memcpy(data, &data_uint64, sizeof(uint64_t));
+ } else {
+ assert(false && "Invalid int bits for CopyBits");
+ }
+ } else {
+ if (bits == 64) {
+ double data_double = src.AsDouble();
+ std::memcpy(data, &data_double, sizeof(double));
+ } else {
+ float data_float = src.AsFloat();
+ uint16_t hex_float = 0;
+ switch (bits) {
+ case 32:
+ std::memcpy(data, &data_float, sizeof(float));
+ break;
+ case 16:
+ case 11:
+ case 10:
+ hex_float = FloatToHexFloat(data_float, bits);
+ std::memcpy(data, &hex_float, sizeof(uint16_t));
+ break;
+ default:
+ assert(false && "Invalid float bits for CopyBits");
+ break;
+ }
+ }
+ }
+
+ while (bit_offset > 7) {
+ ++dst;
+ bit_offset -= 8;
+ }
+
+ ShiftBufferBits(data, ((bit_offset + bits - 1) / 8) + 1, bit_offset);
+ CopyBits(dst, data, bit_offset, bits);
+}
+
+// static
+void BitCopy::CopyBits(uint8_t* dst,
+ const uint8_t* src,
+ uint8_t bit_offset,
+ uint8_t bits) {
+ while (bit_offset + bits > 0) {
+ uint8_t target_bits = bits;
+ if (bit_offset + target_bits > 8)
+ target_bits = 8 - bit_offset;
+
+ uint8_t bit_mask =
+ static_cast<uint8_t>(((1U << target_bits) - 1U) << bit_offset);
+ *dst = (*src & bit_mask) | (*dst & ~bit_mask);
+
+ bit_offset -= bit_offset;
+ bits -= target_bits;
+ ++dst;
+ ++src;
+ }
+}
+
+// static
+uint16_t BitCopy::FloatExponent(const uint32_t hex_float) {
+ uint32_t exponent = ((hex_float >> 23U) & ((1U << 8U) - 1U)) - 127U + 15U;
+ const uint32_t half_exponent_mask = (1U << 5U) - 1U;
+ assert((exponent & ~half_exponent_mask) == 0U);
+ return static_cast<uint16_t>(exponent & half_exponent_mask);
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat(float value, uint8_t bits) {
+ switch (bits) {
+ case 10:
+ return FloatToHexFloat10(value);
+ case 11:
+ return FloatToHexFloat11(value);
+ case 16:
+ return FloatToHexFloat16(value);
+ }
+
+ assert(false && "Invalid bits");
+ return 0;
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat16(const float value) {
+ uint32_t hex;
+ memcpy(&hex, &value, sizeof(float));
+ return static_cast<uint16_t>(FloatSign(hex) << 15U) |
+ static_cast<uint16_t>(FloatExponent(hex) << 10U) | FloatMantissa(hex);
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat11(const float value) {
+ uint32_t hex;
+ memcpy(&hex, &value, sizeof(float));
+ assert(FloatSign(hex) == 0);
+ return static_cast<uint16_t>(FloatExponent(hex) << 6U) |
+ static_cast<uint16_t>(FloatMantissa(hex) >> 4U);
+}
+
+// static
+uint16_t BitCopy::FloatToHexFloat10(const float value) {
+ uint32_t hex;
+ memcpy(&hex, &value, sizeof(float));
+ assert(FloatSign(hex) == 0);
+ return static_cast<uint16_t>(FloatExponent(hex) << 5U) |
+ static_cast<uint16_t>(FloatMantissa(hex) >> 5U);
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/bit_copy.h b/src/vulkan/bit_copy.h
new file mode 100644
index 0000000..31b19bc
--- /dev/null
+++ b/src/vulkan/bit_copy.h
@@ -0,0 +1,82 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_BIT_COPY_H_
+#define SRC_VULKAN_BIT_COPY_H_
+
+#include "src/value.h"
+
+namespace amber {
+namespace vulkan {
+
+class BitCopy {
+ public:
+ // Copy [0, bits) bits of src to [bit_offset, bit_offset + bits) of dst.
+ static void CopyValueToBuffer(uint8_t* dst,
+ const Value& src,
+ uint8_t bit_offset,
+ uint8_t bits);
+
+ private:
+ BitCopy() = delete;
+
+ ~BitCopy() = delete;
+
+ static void ShiftBufferBits(uint8_t* buffer,
+ uint8_t length_bytes,
+ uint8_t shift_bits);
+
+ static void CopyBits(uint8_t* dst,
+ const uint8_t* src,
+ uint8_t bit_offset,
+ uint8_t bits);
+
+ // Convert float to small float format.
+ // See https://www.khronos.org/opengl/wiki/Small_Float_Formats
+ // and https://en.wikipedia.org/wiki/IEEE_754.
+ //
+ // Sign Exponent Mantissa Exponent-Bias
+ // 16 1 5 10 15
+ // 11 0 5 6 15
+ // 10 0 5 5 15
+ // 32 1 8 23 127
+ // 64 1 11 52 1023
+ //
+ // 11 and 10 bits floats are always positive.
+ // 14 bits float is used only RGB9_E5 format but it does not exist in Vulkan.
+ //
+ // For example, 1234 in 32 bits float = 1.0011010010 * 2^10 with base 2.
+ //
+ // 1.0011010010 * 2^10 --> 0 (sign) / 10 + 127 (exp) / 0011010010 (Mantissa)
+ // --> 0x449a4000
+ static uint16_t FloatToHexFloat(float value, uint8_t bits);
+ static uint16_t FloatToHexFloat16(const float value);
+ static uint16_t FloatToHexFloat11(const float value);
+ static uint16_t FloatToHexFloat10(const float value);
+
+ static uint16_t FloatSign(const uint32_t hex_float) {
+ return static_cast<uint16_t>(hex_float >> 31U);
+ }
+
+ static uint16_t FloatExponent(const uint32_t hex_float);
+
+ static uint16_t FloatMantissa(const uint32_t hex_float) {
+ return static_cast<uint16_t>((hex_float & ((1U << 23U) - 1U)) >> 13U);
+ }
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_BIT_COPY_H_
diff --git a/src/vulkan/bit_copy_test.cc b/src/vulkan/bit_copy_test.cc
new file mode 100644
index 0000000..b9367c5
--- /dev/null
+++ b/src/vulkan/bit_copy_test.cc
@@ -0,0 +1,382 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/bit_copy.h"
+
+#include <cstring>
+#include <type_traits>
+
+#include "gtest/gtest.h"
+#include "src/value.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+template <typename T>
+void ExpectBitsEQ(const uint8_t* actual, T expected) {
+ const T* ptr = reinterpret_cast<const T*>(actual);
+ EXPECT_EQ(*ptr, expected);
+}
+
+} // namespace
+
+using BitCopyTest = testing::Test;
+
+TEST_F(BitCopyTest, CopyInt8) {
+ Value value;
+ uint8_t data = 0;
+
+ // 7 0 0 7
+ // 00000000 --> 11011100 (220)
+ // 110111 (55)
+ value.SetIntValue(55);
+ BitCopy::CopyValueToBuffer(&data, value, 2, 6);
+ EXPECT_EQ(data, 220);
+
+ // 7 0 0 7
+ // 11011100 --> 11011111 (223)
+ // 11 (3)
+ value.SetIntValue(3);
+ BitCopy::CopyValueToBuffer(&data, value, 0, 2);
+ EXPECT_EQ(data, 223);
+
+ // 7 0 0 7
+ // 11011111 --> 10110111 (183)
+ // 011011 (27)
+ value.SetIntValue(27);
+ BitCopy::CopyValueToBuffer(&data, value, 1, 6);
+ EXPECT_EQ(data, 183);
+
+ // 7 0 0 7
+ // 10110111 --> 11010111 (215)
+ // 1010 (10)
+ value.SetIntValue(10);
+ BitCopy::CopyValueToBuffer(&data, value, 3, 4);
+ EXPECT_EQ(data, 215);
+}
+
+TEST_F(BitCopyTest, CopyInt16) {
+ Value value;
+ uint8_t data[2] = {};
+
+ // 15 0 15 0
+ // 0000000000000000 --> 1100000011100100 (49380)
+ // 11000000111001 (12345)
+ value.SetIntValue(12345);
+ BitCopy::CopyValueToBuffer(data, value, 2, 14);
+ ExpectBitsEQ<uint16_t>(data, 49380);
+
+ // 15 0 15 0
+ // 1100000011100100 --> 1110100000100100 (59428)
+ // 101000001 (321)
+ value.SetIntValue(321);
+ BitCopy::CopyValueToBuffer(data, value, 5, 9);
+ ExpectBitsEQ<uint16_t>(data, 59428);
+
+ // 15 0 15 0
+ // 1110100000100100 --> 1111000111010111 (61911)
+ // 1000111010111 (4567)
+ value.SetIntValue(4567);
+ BitCopy::CopyValueToBuffer(data, value, 0, 13);
+ ExpectBitsEQ<uint16_t>(data, 61911);
+
+ // 15 0 15 0
+ // 1111000111010111 --> 1001101111011111 (39903)
+ // 001101111011 (891)
+ value.SetIntValue(891);
+ BitCopy::CopyValueToBuffer(data, value, 3, 12);
+ ExpectBitsEQ<uint16_t>(data, 39903);
+}
+
+TEST_F(BitCopyTest, CopyInt32) {
+ Value value;
+ uint8_t data[4] = {};
+
+ // 31 31
+ // 0000000000000000 --> 0001011110001100
+ // 0000000000000000 0010100111000000 (395061696)
+ // 0 0
+ //
+ // 1011110001100
+ // 00101001110 (12345678)
+ value.SetIntValue(12345678);
+ BitCopy::CopyValueToBuffer(data, value, 5, 24);
+ ExpectBitsEQ<uint32_t>(data, 395061696);
+
+ // 31 31
+ // 0001011110001100 --> 0001011110000001
+ // 0010100111000000 1100110111000000 (394382784)
+ // 0 0
+ //
+ // 110000001
+ // 11001101 (98765)
+ value.SetIntValue(98765);
+ BitCopy::CopyValueToBuffer(data, value, 8, 17);
+ ExpectBitsEQ<uint32_t>(data, 394382784);
+
+ // 31 31
+ // 0001011110000001 --> 0001011110000001
+ // 1100110111000000 1100111010001110 (394382990)
+ // 0 0
+ //
+ // 1010001110 (654)
+ value.SetIntValue(654);
+ BitCopy::CopyValueToBuffer(data, value, 0, 10);
+ ExpectBitsEQ<uint32_t>(data, 394382990);
+
+ // 31 31
+ // 0001011110000001 --> 1101001011111100
+ // 1100111010001110 0101011010001110 (3539752590)
+ // 0 0
+ //
+ // 1101001011111100
+ // 010101 (654)
+ value.SetIntValue(3456789);
+ BitCopy::CopyValueToBuffer(data, value, 10, 22);
+ ExpectBitsEQ<uint32_t>(data, 3539752590);
+}
+
+TEST_F(BitCopyTest, CopyInt64) {
+ Value value;
+ uint8_t data[8] = {};
+
+ // 63 63
+ // 0000000000000000 --> 0010001111101110
+ // 0000000000000000 0011111101100110
+ // 0000000000000000 0001011110101100
+ // 0000000000000000 0000000000000000 (2589076543500976128)
+ // 0 0
+ //
+ // 10001111101110
+ // 0011111101100110
+ // 00010111101011
+ // (9876543210987)
+ value.SetIntValue(9876543210987UL);
+ BitCopy::CopyValueToBuffer(data, value, 18, 44);
+ ExpectBitsEQ<uint64_t>(data, 2589076543500976128UL);
+
+ // 63 63
+ // 0010001111101110 --> 0011110001001110
+ // 0011111101100110 1111110000011110
+ // 0001011110101100 1111010011010001
+ // 0000000000000000 0101111101011000 (4345687900345687896)
+ // 0 0
+ //
+ // 11110001001110
+ // 1111110000011110
+ // 1111010011010001
+ // 0101111101011 (543210987543210987)
+ value.SetIntValue(543210987543210987UL);
+ BitCopy::CopyValueToBuffer(data, value, 3, 59);
+ ExpectBitsEQ<uint64_t>(data, 4345687900345687896UL);
+
+ // 63 63
+ // 0011110001001110 --> 0011110001001110
+ // 1111110000011110 1001011111100010
+ // 1111010011010001 1011010011010001
+ // 0101111101011000 0101111101011000 (4345577690411130712)
+ // 0 0
+ //
+ // 110
+ // 1001011111100010
+ // 101
+ // (3456789)
+ value.SetIntValue(3456789UL);
+ BitCopy::CopyValueToBuffer(data, value, 29, 22);
+ ExpectBitsEQ<uint64_t>(data, 4345577690411130712UL);
+}
+
+TEST_F(BitCopyTest, CopyIntMultiple) {
+ uint8_t data[32] = {};
+ Value value;
+
+ // Fill [0, 32) bits of data with
+ // 11(3) / 0010001111(143) / 0001000011(67) / 1000010001(529)
+ value.SetIntValue(529);
+ BitCopy::CopyValueToBuffer(data, value, 0, 10);
+ value.SetIntValue(67);
+ BitCopy::CopyValueToBuffer(data, value, 10, 10);
+ value.SetIntValue(143);
+ BitCopy::CopyValueToBuffer(data, value, 20, 10);
+ value.SetIntValue(3);
+ BitCopy::CopyValueToBuffer(data, value, 30, 2);
+
+ // Fill [32, 96) bits of data with
+ // 00000111010110111100110100010101(123456789) /
+ // 00000000100101101011010000111111(9876543)
+ value.SetIntValue(9876543);
+ BitCopy::CopyValueToBuffer(data, value, 32, 32);
+ value.SetIntValue(123456789);
+ BitCopy::CopyValueToBuffer(data, value, 64, 32);
+
+ // Fill [96, 120) bits of data with
+ // 00011111(31) / 00001001(9) / 01001101(77)
+ value.SetIntValue(77);
+ BitCopy::CopyValueToBuffer(data, value, 96, 8);
+ value.SetIntValue(9);
+ BitCopy::CopyValueToBuffer(data, value, 104, 8);
+ value.SetIntValue(31);
+ BitCopy::CopyValueToBuffer(data, value, 112, 8);
+
+ // Fill [120, 184) bits of data with
+ // 00000001101101101001101101001011
+ // 10100110001100001111001101001110(123456789012345678)
+ value.SetIntValue(123456789012345678UL);
+ BitCopy::CopyValueToBuffer(data, value, 120, 64);
+
+ // Fill [184, 216) bits of data with
+ // 10000011110111011011010010000000(34567890)
+ value.SetIntValue(34567890);
+ BitCopy::CopyValueToBuffer(data, value, 184, 32);
+
+ // Fill [216, 256) bits of data with
+ // 01100011(99) / 1000001000110101(33333) / 11011110(222) / 01101111(111)
+ value.SetIntValue(111);
+ BitCopy::CopyValueToBuffer(data, value, 216, 8);
+ value.SetIntValue(222);
+ BitCopy::CopyValueToBuffer(data, value, 224, 8);
+ value.SetIntValue(33333);
+ BitCopy::CopyValueToBuffer(data, value, 232, 16);
+ value.SetIntValue(99);
+ BitCopy::CopyValueToBuffer(data, value, 248, 8);
+
+ // [0, 32) bits of data
+ ExpectBitsEQ<uint32_t>(data, 3371240977);
+
+ // [32, 96) bits of data
+ ExpectBitsEQ<uint64_t>(&data[4], 530242871234049087UL);
+
+ // [96, 120) bits of data
+ ExpectBitsEQ<uint16_t>(&data[12], 2381);
+ EXPECT_EQ(data[14], 31);
+
+ // [120, 184) bits of data
+ ExpectBitsEQ<uint64_t>(&data[15], 123456789012345678UL);
+
+ // [184, 216) bits of data
+ ExpectBitsEQ<uint32_t>(&data[23], 34567890);
+
+ // [216, 256) bits of data
+ ExpectBitsEQ<uint32_t>(&data[27], 2184568431);
+ EXPECT_EQ(data[31], 99);
+}
+
+TEST_F(BitCopyTest, CopyFloat16) {
+ uint8_t data[2] = {};
+ Value value;
+
+ // 16 bits
+ // Sig / Exp / Mantissa Sig / Exp / Mantissa
+ // 12.34 = 0 / 130 / 4550820 --> 0 / 18 / 555
+ value.SetDoubleValue(12.34);
+ data[0] = 0;
+ data[1] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 0, 16);
+ ExpectBitsEQ<uint16_t>(data, 18987);
+
+ // 11 bits
+ // Sig / Exp / Mantissa Sig / Exp / Mantissa
+ // 5.67 = 0 / 129 / 3502244 --> 17 / 26
+ value.SetDoubleValue(5.67);
+ data[0] = 0;
+ data[1] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 3, 11);
+ ExpectBitsEQ<uint16_t>(data, 8912);
+
+ // 10 bits
+ // Sig / Exp / Mantissa Sig / Exp / Mantissa
+ // 0.89 = 0 / 126 / 6543114 --> 14 / 24
+ value.SetDoubleValue(0.89);
+ data[0] = 0;
+ data[1] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 2, 10);
+ ExpectBitsEQ<uint16_t>(data, 1888);
+}
+
+TEST_F(BitCopyTest, CopyFloat) {
+ uint8_t data[4] = {};
+ Value value;
+
+ // 16 bits
+ // Sig / Exp / Mantissa
+ // 12.34 = 0 / 130 / 4550820
+ value.SetDoubleValue(12.34);
+ BitCopy::CopyValueToBuffer(data, value, 0, 32);
+ ExpectBitsEQ<uint32_t>(data, 1095069860);
+
+ // 11 bits
+ // Sig / Exp / Mantissa
+ // 5.67 = 0 / 129 / 3502244
+ value.SetDoubleValue(5.67);
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 0, 32);
+ ExpectBitsEQ<uint32_t>(data, 1085632676);
+
+ // 10 bits
+ // Sig / Exp / Mantissa
+ // 0.89 = 0 / 126 / 6543114
+ value.SetDoubleValue(0.89);
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 0, 32);
+ ExpectBitsEQ<uint32_t>(data, 1063507722);
+}
+
+TEST_F(BitCopyTest, CopyDouble) {
+ uint8_t data[8] = {};
+ Value value;
+
+ // Sig / Exp / Mantissa
+ // 12.34 = 0 / 1026 / 2443202797848494
+ value.SetDoubleValue(12.34);
+ BitCopy::CopyValueToBuffer(data, value, 0, 64);
+ ExpectBitsEQ<uint64_t>(data, 4623136420479977390);
+
+ // Sig / Exp / Mantissa
+ // 5.67 = 0 / 1025 / 1880252844427182
+ value.SetDoubleValue(5.67);
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+ data[4] = 0;
+ data[5] = 0;
+ data[6] = 0;
+ data[7] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 0, 64);
+ ExpectBitsEQ<uint64_t>(data, 4618069870899185582);
+
+ // Sig / Exp / Mantissa
+ // 0.89 = 0 / 1022 / 3512807709348987
+ value.SetDoubleValue(0.89);
+ data[0] = 0;
+ data[1] = 0;
+ data[2] = 0;
+ data[3] = 0;
+ data[4] = 0;
+ data[5] = 0;
+ data[6] = 0;
+ data[7] = 0;
+ BitCopy::CopyValueToBuffer(data, value, 0, 64);
+ ExpectBitsEQ<uint64_t>(data, 4606191626881995899);
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/buffer.cc b/src/vulkan/buffer.cc
new file mode 100644
index 0000000..4c7d2fe
--- /dev/null
+++ b/src/vulkan/buffer.cc
@@ -0,0 +1,93 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/buffer.h"
+
+namespace amber {
+namespace vulkan {
+
+Buffer::Buffer(VkDevice device,
+ size_t size,
+ const VkPhysicalDeviceMemoryProperties& properties)
+ : Resource(device, size, properties) {}
+
+Buffer::~Buffer() = default;
+
+Result Buffer::Initialize(const VkBufferUsageFlags usage) {
+ Result r = CreateVkBuffer(&buffer_, usage);
+ if (!r.IsSuccess())
+ return r;
+
+ AllocateResult allocate_result = AllocateAndBindMemoryToVkBuffer(
+ buffer_, &memory_, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, false);
+ if (!allocate_result.r.IsSuccess())
+ return allocate_result.r;
+
+ if (CheckMemoryHostAccessible(allocate_result.memory_type_index)) {
+ is_buffer_host_accessible_ = true;
+ return MapMemory(memory_);
+ }
+
+ is_buffer_host_accessible_ = false;
+ return Resource::Initialize();
+}
+
+Result Buffer::CreateVkBufferView(VkFormat format) {
+ VkBufferViewCreateInfo buffer_view_info = {};
+ buffer_view_info.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO;
+ buffer_view_info.buffer = buffer_;
+ buffer_view_info.format = format;
+ buffer_view_info.offset = 0;
+ buffer_view_info.range = VK_WHOLE_SIZE;
+ if (vkCreateBufferView(GetDevice(), &buffer_view_info, nullptr, &view_) !=
+ VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreateBufferView Fail");
+ }
+
+ return {};
+}
+
+void Buffer::CopyToDevice(VkCommandBuffer command) {
+ if (is_buffer_host_accessible_)
+ return;
+
+ VkBufferCopy region = {};
+ region.srcOffset = 0;
+ region.dstOffset = 0;
+ region.size = GetSize();
+
+ vkCmdCopyBuffer(command, GetHostAccessibleBuffer(), buffer_, 1, &region);
+}
+
+void Buffer::Shutdown() {
+ // TODO(jaebaek): Doublecheck what happens if |view_| is VK_NULL_HANDLE on
+ // Android and Windows.
+ if (view_ != VK_NULL_HANDLE) {
+ vkDestroyBufferView(GetDevice(), view_, nullptr);
+ view_ = VK_NULL_HANDLE;
+ }
+
+ if (buffer_ != VK_NULL_HANDLE) {
+ vkDestroyBuffer(GetDevice(), buffer_, nullptr);
+ buffer_ = VK_NULL_HANDLE;
+ }
+
+ if (memory_ != VK_NULL_HANDLE) {
+ vkFreeMemory(GetDevice(), memory_, nullptr);
+ memory_ = VK_NULL_HANDLE;
+ }
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/buffer.h b/src/vulkan/buffer.h
new file mode 100644
index 0000000..9ec1dd2
--- /dev/null
+++ b/src/vulkan/buffer.h
@@ -0,0 +1,60 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_BUFFER_H_
+#define SRC_VULKAN_BUFFER_H_
+
+#include "amber/result.h"
+#include "src/vulkan/resource.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class Buffer : public Resource {
+ public:
+ Buffer(VkDevice device,
+ size_t size,
+ const VkPhysicalDeviceMemoryProperties& properties);
+ ~Buffer() override;
+
+ Result Initialize(const VkBufferUsageFlags usage);
+ VkBuffer GetVkBuffer() const { return buffer_; }
+ Result CreateVkBufferView(VkFormat format);
+ VkBufferView GetVkBufferView() const { return view_; }
+
+ // TODO(jaebaek): Determine copy all or partial data
+ void CopyToDevice(VkCommandBuffer command);
+
+ // Resource
+ VkDeviceMemory GetHostAccessMemory() const override {
+ if (is_buffer_host_accessible_)
+ return memory_;
+
+ return Resource::GetHostAccessMemory();
+ }
+
+ void Shutdown() override;
+
+ private:
+ VkBuffer buffer_ = VK_NULL_HANDLE;
+ VkBufferView view_ = VK_NULL_HANDLE;
+ VkDeviceMemory memory_ = VK_NULL_HANDLE;
+ bool is_buffer_host_accessible_ = false;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_BUFFER_H_
diff --git a/src/vulkan/command.cc b/src/vulkan/command.cc
new file mode 100644
index 0000000..9ff205f
--- /dev/null
+++ b/src/vulkan/command.cc
@@ -0,0 +1,131 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/command.h"
+
+#include <memory>
+
+#include "src/make_unique.h"
+
+namespace amber {
+
+namespace vulkan {
+
+CommandPool::CommandPool(VkDevice device) : device_(device) {}
+
+CommandPool::~CommandPool() = default;
+
+Result CommandPool::Initialize(uint32_t queue_family_index) {
+ VkCommandPoolCreateInfo pool_info = {};
+ pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ pool_info.queueFamilyIndex = queue_family_index;
+
+ if (vkCreateCommandPool(device_, &pool_info, nullptr, &pool_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkCreateCommandPool Fail");
+
+ return {};
+}
+
+void CommandPool::Shutdown() {
+ vkDestroyCommandPool(device_, pool_, nullptr);
+}
+
+CommandBuffer::CommandBuffer(VkDevice device, VkCommandPool pool, VkQueue queue)
+ : device_(device), pool_(pool), queue_(queue) {}
+
+CommandBuffer::~CommandBuffer() = default;
+
+Result CommandBuffer::Initialize() {
+ VkCommandBufferAllocateInfo command_info = {};
+ command_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ command_info.commandPool = pool_;
+ command_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ command_info.commandBufferCount = 1;
+
+ if (vkAllocateCommandBuffers(device_, &command_info, &command_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkAllocateCommandBuffers Fail");
+
+ VkFenceCreateInfo fence_info = {};
+ fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ if (vkCreateFence(device_, &fence_info, nullptr, &fence_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkCreateFence Fail");
+
+ return {};
+}
+
+Result CommandBuffer::BeginIfNotInRecording() {
+ if (state_ == CommandBufferState::kRecording)
+ return {};
+
+ if (state_ != CommandBufferState::kInitial)
+ return Result("Vulkan::Begin CommandBuffer from Not Valid State");
+
+ VkCommandBufferBeginInfo command_begin_info = {};
+ command_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ command_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ if (vkBeginCommandBuffer(command_, &command_begin_info) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkBeginCommandBuffer Fail");
+
+ state_ = CommandBufferState::kRecording;
+ return {};
+}
+
+Result CommandBuffer::End() {
+ if (state_ != CommandBufferState::kRecording)
+ return Result("Vulkan::End CommandBuffer from Not Valid State");
+
+ if (vkEndCommandBuffer(command_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkEndCommandBuffer Fail");
+
+ state_ = CommandBufferState::kExecutable;
+ return {};
+}
+
+Result CommandBuffer::SubmitAndReset() {
+ if (state_ != CommandBufferState::kExecutable)
+ return Result("Vulkan::Submit CommandBuffer from Not Valid State");
+
+ if (vkResetFences(device_, 1, &fence_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkResetFences Fail");
+
+ VkSubmitInfo submit_info = {};
+ submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submit_info.commandBufferCount = 1;
+ submit_info.pCommandBuffers = &command_;
+ if (vkQueueSubmit(queue_, 1, &submit_info, fence_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkQueueSubmit Fail");
+
+ VkResult r =
+ vkWaitForFences(device_, 1, &fence_, VK_TRUE, 100000000 /* nanosecond */);
+ if (r == VK_TIMEOUT)
+ return Result("Vulkan::Calling vkWaitForFences Timeout");
+ if (r != VK_SUCCESS)
+ return Result("Vulkan::Calling vkWaitForFences Fail");
+
+ if (vkResetCommandBuffer(command_, 0) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkResetCommandBuffer Fail");
+
+ state_ = CommandBufferState::kInitial;
+ return {};
+}
+
+void CommandBuffer::Shutdown() {
+ vkDestroyFence(device_, fence_, nullptr);
+ vkFreeCommandBuffers(device_, pool_, 1, &command_);
+}
+
+} // namespace vulkan
+
+} // namespace amber
diff --git a/src/vulkan/command.h b/src/vulkan/command.h
new file mode 100644
index 0000000..97bd521
--- /dev/null
+++ b/src/vulkan/command.h
@@ -0,0 +1,79 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_COMMAND_H_
+#define SRC_VULKAN_COMMAND_H_
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+
+namespace vulkan {
+
+// Command buffer states based on "5.1. Command Buffer Lifecycle" of Vulkan
+// spec
+enum class CommandBufferState : uint8_t {
+ kInitial = 0,
+ kRecording,
+ kExecutable,
+ kPending,
+ kInvalid,
+};
+
+class CommandPool {
+ public:
+ explicit CommandPool(VkDevice device);
+ ~CommandPool();
+
+ Result Initialize(uint32_t queue_family_index);
+ VkCommandPool GetCommandPool() const { return pool_; }
+ void Shutdown();
+
+ private:
+ VkDevice device_ = VK_NULL_HANDLE;
+ VkCommandPool pool_ = VK_NULL_HANDLE;
+};
+
+class CommandBuffer {
+ public:
+ CommandBuffer(VkDevice device, VkCommandPool pool, VkQueue queue);
+ ~CommandBuffer();
+
+ Result Initialize();
+ VkCommandBuffer GetCommandBuffer() const { return command_; }
+ void Shutdown();
+
+ // Do nothing and return if it is already ready to record. If it is in
+ // initial state, call command begin API and make it ready to record.
+ // Otherwise, report error.
+ Result BeginIfNotInRecording();
+
+ Result End();
+ Result SubmitAndReset();
+
+ private:
+ VkDevice device_ = VK_NULL_HANDLE;
+ VkCommandPool pool_ = VK_NULL_HANDLE;
+ VkQueue queue_ = VK_NULL_HANDLE;
+ VkCommandBuffer command_ = VK_NULL_HANDLE;
+ VkFence fence_ = VK_NULL_HANDLE;
+ CommandBufferState state_ = CommandBufferState::kInitial;
+};
+
+} // namespace vulkan
+
+} // namespace amber
+
+#endif // SRC_VULKAN_COMMAND_H_
diff --git a/src/vulkan/descriptor.cc b/src/vulkan/descriptor.cc
new file mode 100644
index 0000000..294e3b6
--- /dev/null
+++ b/src/vulkan/descriptor.cc
@@ -0,0 +1,58 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/descriptor.h"
+
+#include <cassert>
+
+namespace amber {
+namespace vulkan {
+
+VkDescriptorType ToVkDescriptorType(DescriptorType type) {
+ switch (type) {
+ case DescriptorType::kStorageImage:
+ return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
+ case DescriptorType::kSampler:
+ return VK_DESCRIPTOR_TYPE_SAMPLER;
+ case DescriptorType::kSampledImage:
+ return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
+ case DescriptorType::kCombinedImageSampler:
+ return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ case DescriptorType::kUniformTexelBuffer:
+ return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
+ case DescriptorType::kStorageTexelBuffer:
+ return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
+ case DescriptorType::kStorageBuffer:
+ return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
+ case DescriptorType::kUniformBuffer:
+ return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ case DescriptorType::kDynamicUniformBuffer:
+ return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC;
+ case DescriptorType::kDynamicStorageBuffer:
+ return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC;
+ case DescriptorType::kInputAttachment:
+ return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
+ }
+
+ assert(false && "Unknown resource type");
+ return VK_DESCRIPTOR_TYPE_SAMPLER;
+}
+
+Descriptor::Descriptor(DescriptorType type, uint32_t desc_set, uint32_t binding)
+ : type_(type), descriptor_set_(desc_set), binding_(binding) {}
+
+Descriptor::~Descriptor() = default;
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/descriptor.h b/src/vulkan/descriptor.h
new file mode 100644
index 0000000..ea24733
--- /dev/null
+++ b/src/vulkan/descriptor.h
@@ -0,0 +1,92 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_DESCRIPTOR_H_
+#define SRC_VULKAN_DESCRIPTOR_H_
+
+#include <memory>
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+enum class DescriptorType : uint8_t {
+ kStorageImage = 0,
+ kSampler,
+ kSampledImage,
+ kCombinedImageSampler,
+ kUniformTexelBuffer,
+ kStorageTexelBuffer,
+ kStorageBuffer,
+ kUniformBuffer,
+ kDynamicUniformBuffer,
+ kDynamicStorageBuffer,
+ kInputAttachment,
+};
+
+VkDescriptorType ToVkDescriptorType(DescriptorType type);
+
+class Descriptor {
+ public:
+ Descriptor(DescriptorType type, uint32_t desc_set, uint32_t binding);
+
+ ~Descriptor();
+
+ uint32_t GetDescriptorSet() const { return descriptor_set_; }
+ uint32_t GetBinding() const { return binding_; }
+ bool operator<(const Descriptor& r) {
+ if (descriptor_set_ == r.descriptor_set_)
+ return binding_ < r.binding_;
+ return descriptor_set_ < r.descriptor_set_;
+ }
+
+ DescriptorType GetType() const { return type_; }
+
+ bool IsStorageImage() const { return type_ == DescriptorType::kStorageImage; }
+ bool IsSampler() const { return type_ == DescriptorType::kSampler; }
+ bool IsSampledImage() const { return type_ == DescriptorType::kSampledImage; }
+ bool IsCombinedImageSampler() const {
+ return type_ == DescriptorType::kCombinedImageSampler;
+ }
+ bool IsUniformTexelBuffer() const {
+ return type_ == DescriptorType::kUniformTexelBuffer;
+ }
+ bool IsStorageTexelBuffer() const {
+ return type_ == DescriptorType::kStorageTexelBuffer;
+ }
+ bool IsStorageBuffer() const {
+ return type_ == DescriptorType::kStorageBuffer;
+ }
+ bool IsUniformBuffer() const {
+ return type_ == DescriptorType::kUniformBuffer;
+ }
+ bool IsDynamicUniformBuffer() const {
+ return type_ == DescriptorType::kDynamicUniformBuffer;
+ }
+ bool IsDynamicStorageBuffer() const {
+ return type_ == DescriptorType::kDynamicStorageBuffer;
+ }
+
+ private:
+ DescriptorType type_ = DescriptorType::kSampledImage;
+ uint32_t descriptor_set_ = 0;
+ uint32_t binding_ = 0;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_DESCRIPTOR_H_
diff --git a/src/vulkan/device.cc b/src/vulkan/device.cc
new file mode 100644
index 0000000..c5dc430
--- /dev/null
+++ b/src/vulkan/device.cc
@@ -0,0 +1,155 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/device.h"
+
+#include <memory>
+#include <vector>
+
+#include "src/make_unique.h"
+
+namespace amber {
+
+namespace vulkan {
+
+Device::Device() = default;
+Device::Device(VkDevice device) : device_(device), destroy_device_(false) {}
+Device::~Device() = default;
+
+void Device::Shutdown() {
+ if (destroy_device_) {
+ vkDestroyDevice(device_, nullptr);
+ vkDestroyInstance(instance_, nullptr);
+ }
+}
+
+Result Device::Initialize() {
+ if (device_ == VK_NULL_HANDLE) {
+ Result r = CreateInstance();
+ if (!r.IsSuccess())
+ return r;
+
+ r = ChoosePhysicalDevice();
+ if (!r.IsSuccess())
+ return r;
+
+ r = CreateDevice();
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ if (queue_ == VK_NULL_HANDLE) {
+ vkGetDeviceQueue(device_, queue_family_index_, 0, &queue_);
+ if (queue_ == VK_NULL_HANDLE)
+ return Result("Vulkan::Calling vkGetDeviceQueue Fail");
+ }
+ return {};
+}
+
+bool Device::ChooseQueueFamilyIndex(const VkPhysicalDevice& physical_device) {
+ uint32_t count;
+ std::vector<VkQueueFamilyProperties> properties;
+
+ vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &count, nullptr);
+ properties.resize(count);
+ vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &count,
+ properties.data());
+
+ for (uint32_t i = 0; i < count; ++i) {
+ if (properties[i].queueFlags &
+ (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT)) {
+ queue_family_flags_ = properties[i].queueFlags;
+ queue_family_index_ = i;
+ return true;
+ }
+ }
+
+ for (uint32_t i = 0; i < count; ++i) {
+ if (properties[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ queue_family_flags_ = properties[i].queueFlags;
+ queue_family_index_ = i;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Result Device::CreateInstance() {
+ VkApplicationInfo appInfo = {};
+ appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0);
+
+ VkInstanceCreateInfo instInfo = {};
+ instInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ instInfo.pApplicationInfo = &appInfo;
+ // TODO(jaebaek): Enable layers, extensions
+
+ if (vkCreateInstance(&instInfo, nullptr, &instance_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkCreateInstance Fail");
+
+ return {};
+}
+
+Result Device::ChoosePhysicalDevice() {
+ uint32_t count;
+ std::vector<VkPhysicalDevice> physical_devices;
+
+ if (vkEnumeratePhysicalDevices(instance_, &count, nullptr) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkEnumeratePhysicalDevices Fail");
+ physical_devices.resize(count);
+ if (vkEnumeratePhysicalDevices(instance_, &count, physical_devices.data()) !=
+ VK_SUCCESS)
+ return Result("Vulkan::Calling vkEnumeratePhysicalDevices Fail");
+
+ for (uint32_t i = 0; i < count; ++i) {
+ VkPhysicalDeviceProperties property;
+ vkGetPhysicalDeviceProperties(physical_devices[i], &property);
+ // TODO(jaebaek): Check physical device property
+
+ if (ChooseQueueFamilyIndex(physical_devices[i])) {
+ physical_device_ = physical_devices[i];
+ vkGetPhysicalDeviceMemoryProperties(physical_device_,
+ &physical_memory_properties_);
+ return {};
+ }
+ }
+
+ return Result("Vulkan::No physical device supports Vulkan");
+}
+
+Result Device::CreateDevice() {
+ VkDeviceQueueCreateInfo queue_info;
+ const float priorities[] = {1.0f};
+
+ queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queue_info.queueFamilyIndex = queue_family_index_;
+ queue_info.queueCount = 1;
+ queue_info.pQueuePriorities = priorities;
+
+ VkDeviceCreateInfo info = {};
+ info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ info.pQueueCreateInfos = &queue_info;
+ info.queueCreateInfoCount = 1;
+ // TODO(jaebaek): Enable layers, extensions, features
+
+ if (vkCreateDevice(physical_device_, &info, nullptr, &device_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkCreateDevice Fail");
+
+ return {};
+}
+
+} // namespace vulkan
+
+} // namespace amber
diff --git a/src/vulkan/device.h b/src/vulkan/device.h
new file mode 100644
index 0000000..634e07d
--- /dev/null
+++ b/src/vulkan/device.h
@@ -0,0 +1,76 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_DEVICE_H_
+#define SRC_VULKAN_DEVICE_H_
+
+#include <memory>
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+
+namespace vulkan {
+
+class Device {
+ public:
+ Device();
+ explicit Device(VkDevice device);
+ ~Device();
+
+ Result Initialize();
+ void Shutdown();
+
+ VkDevice GetDevice() const { return device_; }
+ uint32_t GetQueueFamilyIndex() const { return queue_family_index_; }
+ VkQueue GetQueue() const { return queue_; }
+ const VkPhysicalDeviceMemoryProperties& GetPhysicalMemoryProperties() const {
+ return physical_memory_properties_;
+ }
+
+ private:
+ Result CreateInstance();
+
+ // Get a physical device by checking if the physical device has a proper
+ // queue family.
+ Result ChoosePhysicalDevice();
+
+ // Return true if |physical_device| has a queue family that supports both
+ // graphics and compute or only a compute pipeline. If the proper queue
+ // family exists, |queue_family_index_| and |queue_family_flags_| will have
+ // the queue family index and flags, respectively. Return false if the proper
+ // queue family does not exist.
+ bool ChooseQueueFamilyIndex(const VkPhysicalDevice& physical_device);
+
+ // Create a logical device.
+ Result CreateDevice();
+
+ VkInstance instance_ = VK_NULL_HANDLE;
+ VkPhysicalDevice physical_device_ = VK_NULL_HANDLE;
+ VkPhysicalDeviceMemoryProperties physical_memory_properties_ = {};
+ VkDevice device_ = VK_NULL_HANDLE;
+ VkQueueFlags queue_family_flags_ = 0;
+ uint32_t queue_family_index_ = 0;
+
+ VkQueue queue_ = VK_NULL_HANDLE;
+
+ bool destroy_device_ = true;
+};
+
+} // namespace vulkan
+
+} // namespace amber
+
+#endif // SRC_VULKAN_DEVICE_H_
diff --git a/src/vulkan/engine_vulkan.cc b/src/vulkan/engine_vulkan.cc
new file mode 100644
index 0000000..4618296
--- /dev/null
+++ b/src/vulkan/engine_vulkan.cc
@@ -0,0 +1,278 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/engine_vulkan.h"
+
+#include <algorithm>
+
+#include "src/make_unique.h"
+#include "src/vulkan/format_data.h"
+#include "src/vulkan/graphics_pipeline.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+const uint32_t kFramebufferWidth = 250;
+const uint32_t kFramebufferHeight = 250;
+const VkFormat kDefaultColorFormat = VK_FORMAT_R8G8B8A8_UNORM;
+
+VkShaderStageFlagBits ToVkShaderStage(ShaderType type) {
+ switch (type) {
+ case ShaderType::kGeometry:
+ return VK_SHADER_STAGE_GEOMETRY_BIT;
+ case ShaderType::kFragment:
+ return VK_SHADER_STAGE_FRAGMENT_BIT;
+ case ShaderType::kVertex:
+ return VK_SHADER_STAGE_VERTEX_BIT;
+ case ShaderType::kTessellationControl:
+ return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
+ case ShaderType::kTessellationEvaluation:
+ return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
+ case ShaderType::kCompute:
+ return VK_SHADER_STAGE_COMPUTE_BIT;
+ }
+
+ // Unreachable
+ return VK_SHADER_STAGE_FRAGMENT_BIT;
+}
+
+} // namespace
+
+EngineVulkan::EngineVulkan() : Engine() {}
+
+EngineVulkan::~EngineVulkan() = default;
+
+Result EngineVulkan::InitDeviceAndCreateCommand() {
+ Result r = device_->Initialize();
+ if (!r.IsSuccess())
+ return r;
+
+ if (!pool_) {
+ pool_ = MakeUnique<CommandPool>(device_->GetDevice());
+ r = pool_->Initialize(device_->GetQueueFamilyIndex());
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ return {};
+}
+
+Result EngineVulkan::Initialize() {
+ if (device_)
+ return Result("Vulkan::Set device_ already exists");
+
+ device_ = MakeUnique<Device>();
+ return InitDeviceAndCreateCommand();
+}
+
+Result EngineVulkan::InitializeWithDevice(void* default_device) {
+ if (device_)
+ return Result("Vulkan::Set device_ already exists");
+
+ VkDevice device = static_cast<VkDevice>(default_device);
+ if (device == VK_NULL_HANDLE)
+ return Result("Vulkan::Set VK_NULL_HANDLE is given");
+
+ device_ = MakeUnique<Device>(device);
+ return InitDeviceAndCreateCommand();
+}
+
+Result EngineVulkan::Shutdown() {
+ for (auto it = modules_.begin(); it != modules_.end(); ++it)
+ vkDestroyShaderModule(device_->GetDevice(), it->second, nullptr);
+
+ pipeline_->Shutdown();
+ pool_->Shutdown();
+ device_->Shutdown();
+ return {};
+}
+
+Result EngineVulkan::AddRequirement(Feature feature, const Format* fmt) {
+ auto it = std::find_if(requirements_.begin(), requirements_.end(),
+ [&feature](const EngineVulkan::Requirement& req) {
+ return req.feature == feature;
+ });
+ if (it != requirements_.end())
+ return Result("Vulkan::Feature Already Exists");
+
+ requirements_.push_back({feature, fmt});
+ return {};
+}
+
+Result EngineVulkan::CreatePipeline(PipelineType type) {
+ if (type == PipelineType::kCompute)
+ return Result("Vulkan::Compute Pipeline Not Implemented");
+
+ VkFormat frame_buffer_format = kDefaultColorFormat;
+ auto it_frame_buffer =
+ std::find_if(requirements_.begin(), requirements_.end(),
+ [](const EngineVulkan::Requirement& req) {
+ return req.feature == Feature::kFramebuffer;
+ });
+ if (it_frame_buffer != requirements_.end()) {
+ frame_buffer_format = ToVkFormat(it_frame_buffer->format->GetFormatType());
+ }
+
+ VkFormat depth_stencil_format = VK_FORMAT_UNDEFINED;
+ auto it_depth_stencil =
+ std::find_if(requirements_.begin(), requirements_.end(),
+ [](const EngineVulkan::Requirement& req) {
+ return req.feature == Feature::kDepthStencil;
+ });
+ if (it_depth_stencil != requirements_.end()) {
+ depth_stencil_format =
+ ToVkFormat(it_depth_stencil->format->GetFormatType());
+ }
+
+ pipeline_ = MakeUnique<GraphicsPipeline>(
+ type, device_->GetDevice(), device_->GetPhysicalMemoryProperties(),
+ frame_buffer_format, depth_stencil_format, GetShaderStageInfo());
+
+ return pipeline_->AsGraphics()->Initialize(
+ kFramebufferWidth, kFramebufferHeight, pool_->GetCommandPool(),
+ device_->GetQueue());
+}
+
+Result EngineVulkan::SetShader(ShaderType type,
+ const std::vector<uint32_t>& data) {
+ if (type == ShaderType::kCompute)
+ return Result("Vulkan::Compute Pipeline Not Implemented");
+
+ VkShaderModuleCreateInfo info = {};
+ info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ info.codeSize = data.size() * sizeof(uint32_t);
+ info.pCode = data.data();
+
+ auto it = modules_.find(type);
+ if (it != modules_.end())
+ return Result("Vulkan::Setting Duplicated Shader Types Fail");
+
+ VkShaderModule shader;
+ if (vkCreateShaderModule(device_->GetDevice(), &info, nullptr, &shader) !=
+ VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreateShaderModule Fail");
+ }
+
+ modules_[type] = shader;
+ return {};
+}
+
+std::vector<VkPipelineShaderStageCreateInfo>
+EngineVulkan::GetShaderStageInfo() {
+ std::vector<VkPipelineShaderStageCreateInfo> stage_info(modules_.size());
+ uint32_t stage_count = 0;
+ for (auto it : modules_) {
+ stage_info[stage_count] = {};
+ stage_info[stage_count].sType =
+ VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ stage_info[stage_count].stage = ToVkShaderStage(it.first);
+ stage_info[stage_count].module = it.second;
+ // TODO(jaebaek): Handle entry point command
+ stage_info[stage_count].pName = "main";
+ ++stage_count;
+ }
+ return stage_info;
+}
+
+Result EngineVulkan::SetBuffer(BufferType type,
+ uint8_t location,
+ const Format& format,
+ const std::vector<Value>& values) {
+ if (!pipeline_)
+ return Result("Vulkan::SetBuffer no Pipeline exists");
+
+ // TODO(jaebaek): Doublecheck those buffers are only for the graphics
+ // pipeline.
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::SetBuffer for Non-Graphics Pipeline");
+
+ pipeline_->AsGraphics()->SetBuffer(type, location, format, values);
+ return {};
+}
+
+Result EngineVulkan::ExecuteClearColor(const ClearColorCommand* command) {
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::Clear Color Command for Non-Graphics Pipeline");
+
+ return pipeline_->AsGraphics()->SetClearColor(
+ command->GetR(), command->GetG(), command->GetB(), command->GetA());
+}
+
+Result EngineVulkan::ExecuteClearStencil(const ClearStencilCommand* command) {
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::Clear Stencil Command for Non-Graphics Pipeline");
+
+ return pipeline_->AsGraphics()->SetClearStencil(command->GetValue());
+}
+
+Result EngineVulkan::ExecuteClearDepth(const ClearDepthCommand* command) {
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::Clear Depth Command for Non-Graphics Pipeline");
+
+ return pipeline_->AsGraphics()->SetClearDepth(command->GetValue());
+}
+
+Result EngineVulkan::ExecuteClear(const ClearCommand*) {
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::Clear Command for Non-Graphics Pipeline");
+
+ return pipeline_->AsGraphics()->Clear();
+}
+
+Result EngineVulkan::ExecuteDrawRect(const DrawRectCommand*) {
+ return Result("Vulkan::ExecuteDrawRect Not Implemented");
+}
+
+Result EngineVulkan::ExecuteDrawArrays(const DrawArraysCommand*) {
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::DrawArrays for Non-Graphics Pipeline");
+
+ return pipeline_->AsGraphics()->Draw();
+}
+
+Result EngineVulkan::ExecuteCompute(const ComputeCommand*) {
+ return Result("Vulkan::ExecuteCompute Not Implemented");
+}
+
+Result EngineVulkan::ExecuteEntryPoint(const EntryPointCommand*) {
+ return Result("Vulkan::ExecuteEntryPoint Not Implemented");
+}
+
+Result EngineVulkan::ExecutePatchParameterVertices(
+ const PatchParameterVerticesCommand*) {
+ return Result("Vulkan::ExecutePatch Not Implemented");
+}
+
+Result EngineVulkan::ExecuteProbe(const ProbeCommand* command) {
+ if (!pipeline_->IsGraphics())
+ return Result("Vulkan::Probe FrameBuffer for Non-Graphics Pipeline");
+
+ return pipeline_->AsGraphics()->Probe(command);
+}
+
+Result EngineVulkan::ExecuteProbeSSBO(const ProbeSSBOCommand*) {
+ return Result("Vulkan::ExecuteProbeSSBO Not Implemented");
+}
+
+Result EngineVulkan::ExecuteBuffer(const BufferCommand*) {
+ return Result("Vulkan::ExecuteBuffer Not Implemented");
+}
+
+Result EngineVulkan::ExecuteTolerance(const ToleranceCommand*) {
+ return Result("Vulkan::ExecuteTolerance Not Implemented");
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/engine_vulkan.h b/src/vulkan/engine_vulkan.h
new file mode 100644
index 0000000..c8c2a00
--- /dev/null
+++ b/src/vulkan/engine_vulkan.h
@@ -0,0 +1,85 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_ENGINE_VULKAN_H_
+#define SRC_VULKAN_ENGINE_VULKAN_H_
+
+#include <memory>
+#include <unordered_map>
+
+#include "src/cast_hash.h"
+#include "src/engine.h"
+#include "src/vulkan/command.h"
+#include "src/vulkan/device.h"
+#include "src/vulkan/pipeline.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class EngineVulkan : public Engine {
+ public:
+ EngineVulkan();
+ ~EngineVulkan() override;
+
+ // Engine
+ Result Initialize() override;
+ Result InitializeWithDevice(void* default_device) override;
+ Result Shutdown() override;
+ Result AddRequirement(Feature feature, const Format*) override;
+ Result CreatePipeline(PipelineType type) override;
+ Result SetShader(ShaderType type, const std::vector<uint32_t>& data) override;
+ Result SetBuffer(BufferType type,
+ uint8_t location,
+ const Format& format,
+ const std::vector<Value>& data) override;
+ Result ExecuteClearColor(const ClearColorCommand* cmd) override;
+ Result ExecuteClearStencil(const ClearStencilCommand* cmd) override;
+ Result ExecuteClearDepth(const ClearDepthCommand* cmd) override;
+ Result ExecuteClear(const ClearCommand* cmd) override;
+ Result ExecuteDrawRect(const DrawRectCommand* cmd) override;
+ Result ExecuteDrawArrays(const DrawArraysCommand* cmd) override;
+ Result ExecuteCompute(const ComputeCommand* cmd) override;
+ Result ExecuteEntryPoint(const EntryPointCommand* cmd) override;
+ Result ExecutePatchParameterVertices(
+ const PatchParameterVerticesCommand* cmd) override;
+ Result ExecuteProbe(const ProbeCommand* cmd) override;
+ Result ExecuteProbeSSBO(const ProbeSSBOCommand* cmd) override;
+ Result ExecuteBuffer(const BufferCommand* cmd) override;
+ Result ExecuteTolerance(const ToleranceCommand* cmd) override;
+
+ private:
+ Result InitDeviceAndCreateCommand();
+
+ std::vector<VkPipelineShaderStageCreateInfo> GetShaderStageInfo();
+
+ std::unique_ptr<Device> device_;
+ std::unique_ptr<CommandPool> pool_;
+ std::unique_ptr<Pipeline> pipeline_;
+
+ std::unordered_map<ShaderType, VkShaderModule, CastHash<ShaderType>> modules_;
+
+ struct Requirement {
+ Feature feature;
+ const Format* format;
+ };
+
+ std::vector<Requirement> requirements_;
+ std::vector<Requirement>::iterator FindFeature(Feature feature);
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_ENGINE_VULKAN_H_
diff --git a/src/vulkan/find_vulkan.cmake b/src/vulkan/find_vulkan.cmake
new file mode 100644
index 0000000..630346a
--- /dev/null
+++ b/src/vulkan/find_vulkan.cmake
@@ -0,0 +1,77 @@
+# Copyright 2018 The Amber 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.
+
+
+# Include this file to find Vulkan and and set up compilation and linking.
+
+
+# Export these settings to the includer.
+set(Vulkan_FOUND FALSE)
+set(VULKAN_LIB "")
+
+
+# Our first choice is to pick up the Vulkan headers from an enclosing project.
+# And if that's the case, then use Vulkan libraries as specified by
+# Vulkan_LIBRARIES, with a default library of "vulkan".
+set(X "${Vulkan-Headers_SOURCE_DIR}/include")
+if (IS_DIRECTORY "${X}")
+ message(STATUS "Amber: Using Vulkan header dir ${X}")
+ list(APPEND CMAKE_REQUIRED_INCLUDES "${X}")
+ # Add the directory to the list of include paths, before any others.
+ include_directories(BEFORE "${X}")
+ CHECK_INCLUDE_FILE(vulkan/vulkan.h HAVE_VULKAN_HEADER)
+
+ if (${HAVE_VULKAN_HEADER})
+ if ("${Vulkan_LIBRARIES}" STREQUAL "")
+ message(STATUS "Amber: Defaulting to Vulkan library: vulkan")
+ set(VULKAN_LIB vulkan)
+ else()
+ message(STATUS "Amber: Using specified Vulkan libraries: ${Vulkan_LIBRARIES}")
+ set(VULKAN_LIB "${Vulkan_LIBRARIES}")
+ endif()
+ # For now assume we have Vulkan. We have its header, but we haven't checked
+ # for the library.
+ # TODO(dneto): Actually check for the libraries.
+ set(Vulkan_FOUND TRUE)
+ endif()
+endif()
+unset(X)
+
+if (NOT ${Vulkan_FOUND})
+ # If we aren't already building a Vulkan library, then use CMake to find it.
+ if(NOT ${CMAKE_VERSION} VERSION_LESS "3.7")
+ # LunarG added FindVulkan support to CMake 3.7. If you have the Vulkan SDK
+ # published by LunarG, then set environment variables:
+ # VULKAN_SDK should point to the platform-specific SDK directory containing
+ # the include and lib directories.
+ # VK_ICD_FILENAMES should point to ICD JSON file.
+
+ # Example, with the LunarG SDK macOS edition with MoltenVK:
+ # export VULKAN_SDK="$HOME/vulkan-macos-1.1.85.0/macOS"
+ # export VK_ICD_FILENAMES="$VULKAN_SDK/etc/vulkan/icd/MoltenVK_icd.json"
+ # See https://cmake.org/cmake/help/v3.7/module/FindVulkan.html
+ find_package(Vulkan)
+ if(${Vulkan_FOUND})
+ message(STATUS "Amber: Using Vulkan from Vulkan SDK at $ENV{VULKAN_SDK}")
+ # Use the imported library target set up by find_package.
+ set(VULKAN_LIB Vulkan::Vulkan)
+ # Add the Vulkan include directory to the list of include paths.
+ include_directories("${Vulkan_INCLUDE_DIRS}")
+ endif()
+ endif()
+endif()
+
+if (NOT ${Vulkan_FOUND})
+ message(STATUS "Amber: Did not find Vulkan")
+endif()
diff --git a/src/vulkan/format_data.cc b/src/vulkan/format_data.cc
new file mode 100644
index 0000000..a78aad5
--- /dev/null
+++ b/src/vulkan/format_data.cc
@@ -0,0 +1,299 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+
+VkFormat ToVkFormat(FormatType type) {
+ switch (type) {
+ case FormatType::kUnknown:
+ return VK_FORMAT_UNDEFINED;
+ case FormatType::kA1R5G5B5_UNORM_PACK16:
+ return VK_FORMAT_A1R5G5B5_UNORM_PACK16;
+ case FormatType::kA2B10G10R10_SINT_PACK32:
+ return VK_FORMAT_A2B10G10R10_SINT_PACK32;
+ case FormatType::kA2B10G10R10_SNORM_PACK32:
+ return VK_FORMAT_A2B10G10R10_SNORM_PACK32;
+ case FormatType::kA2B10G10R10_SSCALED_PACK32:
+ return VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
+ case FormatType::kA2B10G10R10_UINT_PACK32:
+ return VK_FORMAT_A2B10G10R10_UINT_PACK32;
+ case FormatType::kA2B10G10R10_UNORM_PACK32:
+ return VK_FORMAT_A2B10G10R10_UNORM_PACK32;
+ case FormatType::kA2B10G10R10_USCALED_PACK32:
+ return VK_FORMAT_A2B10G10R10_USCALED_PACK32;
+ case FormatType::kA2R10G10B10_SINT_PACK32:
+ return VK_FORMAT_A2R10G10B10_SINT_PACK32;
+ case FormatType::kA2R10G10B10_SNORM_PACK32:
+ return VK_FORMAT_A2R10G10B10_SNORM_PACK32;
+ case FormatType::kA2R10G10B10_SSCALED_PACK32:
+ return VK_FORMAT_A2R10G10B10_SSCALED_PACK32;
+ case FormatType::kA2R10G10B10_UINT_PACK32:
+ return VK_FORMAT_A2R10G10B10_UINT_PACK32;
+ case FormatType::kA2R10G10B10_UNORM_PACK32:
+ return VK_FORMAT_A2R10G10B10_UNORM_PACK32;
+ case FormatType::kA2R10G10B10_USCALED_PACK32:
+ return VK_FORMAT_A2R10G10B10_USCALED_PACK32;
+ case FormatType::kA8B8G8R8_SINT_PACK32:
+ return VK_FORMAT_A8B8G8R8_SINT_PACK32;
+ case FormatType::kA8B8G8R8_SNORM_PACK32:
+ return VK_FORMAT_A8B8G8R8_SNORM_PACK32;
+ case FormatType::kA8B8G8R8_SRGB_PACK32:
+ return VK_FORMAT_A8B8G8R8_SRGB_PACK32;
+ case FormatType::kA8B8G8R8_SSCALED_PACK32:
+ return VK_FORMAT_A8B8G8R8_SSCALED_PACK32;
+ case FormatType::kA8B8G8R8_UINT_PACK32:
+ return VK_FORMAT_A8B8G8R8_UINT_PACK32;
+ case FormatType::kA8B8G8R8_UNORM_PACK32:
+ return VK_FORMAT_A8B8G8R8_UNORM_PACK32;
+ case FormatType::kA8B8G8R8_USCALED_PACK32:
+ return VK_FORMAT_A8B8G8R8_USCALED_PACK32;
+ case FormatType::kB10G11R11_UFLOAT_PACK32:
+ return VK_FORMAT_B10G11R11_UFLOAT_PACK32;
+ case FormatType::kB4G4R4A4_UNORM_PACK16:
+ return VK_FORMAT_B4G4R4A4_UNORM_PACK16;
+ case FormatType::kB5G5R5A1_UNORM_PACK16:
+ return VK_FORMAT_B5G5R5A1_UNORM_PACK16;
+ case FormatType::kB5G6R5_UNORM_PACK16:
+ return VK_FORMAT_B5G6R5_UNORM_PACK16;
+ case FormatType::kB8G8R8A8_SINT:
+ return VK_FORMAT_B8G8R8A8_SINT;
+ case FormatType::kB8G8R8A8_SNORM:
+ return VK_FORMAT_B8G8R8A8_SNORM;
+ case FormatType::kB8G8R8A8_SRGB:
+ return VK_FORMAT_B8G8R8A8_SRGB;
+ case FormatType::kB8G8R8A8_SSCALED:
+ return VK_FORMAT_B8G8R8A8_SSCALED;
+ case FormatType::kB8G8R8A8_UINT:
+ return VK_FORMAT_B8G8R8A8_UINT;
+ case FormatType::kB8G8R8A8_UNORM:
+ return VK_FORMAT_B8G8R8A8_UNORM;
+ case FormatType::kB8G8R8A8_USCALED:
+ return VK_FORMAT_B8G8R8A8_USCALED;
+ case FormatType::kB8G8R8_SINT:
+ return VK_FORMAT_B8G8R8_SINT;
+ case FormatType::kB8G8R8_SNORM:
+ return VK_FORMAT_B8G8R8_SNORM;
+ case FormatType::kB8G8R8_SRGB:
+ return VK_FORMAT_B8G8R8_SRGB;
+ case FormatType::kB8G8R8_SSCALED:
+ return VK_FORMAT_B8G8R8_SSCALED;
+ case FormatType::kB8G8R8_UINT:
+ return VK_FORMAT_B8G8R8_UINT;
+ case FormatType::kB8G8R8_UNORM:
+ return VK_FORMAT_B8G8R8_UNORM;
+ case FormatType::kB8G8R8_USCALED:
+ return VK_FORMAT_B8G8R8_USCALED;
+ case FormatType::kD16_UNORM:
+ return VK_FORMAT_D16_UNORM;
+ case FormatType::kD16_UNORM_S8_UINT:
+ return VK_FORMAT_D16_UNORM_S8_UINT;
+ case FormatType::kD24_UNORM_S8_UINT:
+ return VK_FORMAT_D24_UNORM_S8_UINT;
+ case FormatType::kD32_SFLOAT:
+ return VK_FORMAT_D32_SFLOAT;
+ case FormatType::kD32_SFLOAT_S8_UINT:
+ return VK_FORMAT_D32_SFLOAT_S8_UINT;
+ case FormatType::kR16G16B16A16_SFLOAT:
+ return VK_FORMAT_R16G16B16A16_SFLOAT;
+ case FormatType::kR16G16B16A16_SINT:
+ return VK_FORMAT_R16G16B16A16_SINT;
+ case FormatType::kR16G16B16A16_SNORM:
+ return VK_FORMAT_R16G16B16A16_SNORM;
+ case FormatType::kR16G16B16A16_SSCALED:
+ return VK_FORMAT_R16G16B16A16_SSCALED;
+ case FormatType::kR16G16B16A16_UINT:
+ return VK_FORMAT_R16G16B16A16_UINT;
+ case FormatType::kR16G16B16A16_UNORM:
+ return VK_FORMAT_R16G16B16A16_UNORM;
+ case FormatType::kR16G16B16A16_USCALED:
+ return VK_FORMAT_R16G16B16A16_USCALED;
+ case FormatType::kR16G16B16_SFLOAT:
+ return VK_FORMAT_R16G16B16_SFLOAT;
+ case FormatType::kR16G16B16_SINT:
+ return VK_FORMAT_R16G16B16_SINT;
+ case FormatType::kR16G16B16_SNORM:
+ return VK_FORMAT_R16G16B16_SNORM;
+ case FormatType::kR16G16B16_SSCALED:
+ return VK_FORMAT_R16G16B16_SSCALED;
+ case FormatType::kR16G16B16_UINT:
+ return VK_FORMAT_R16G16B16_UINT;
+ case FormatType::kR16G16B16_UNORM:
+ return VK_FORMAT_R16G16B16_UNORM;
+ case FormatType::kR16G16B16_USCALED:
+ return VK_FORMAT_R16G16B16_USCALED;
+ case FormatType::kR16G16_SFLOAT:
+ return VK_FORMAT_R16G16_SFLOAT;
+ case FormatType::kR16G16_SINT:
+ return VK_FORMAT_R16G16_SINT;
+ case FormatType::kR16G16_SNORM:
+ return VK_FORMAT_R16G16_SNORM;
+ case FormatType::kR16G16_SSCALED:
+ return VK_FORMAT_R16G16_SSCALED;
+ case FormatType::kR16G16_UINT:
+ return VK_FORMAT_R16G16_UINT;
+ case FormatType::kR16G16_UNORM:
+ return VK_FORMAT_R16G16_UNORM;
+ case FormatType::kR16G16_USCALED:
+ return VK_FORMAT_R16G16_USCALED;
+ case FormatType::kR16_SFLOAT:
+ return VK_FORMAT_R16_SFLOAT;
+ case FormatType::kR16_SINT:
+ return VK_FORMAT_R16_SINT;
+ case FormatType::kR16_SNORM:
+ return VK_FORMAT_R16_SNORM;
+ case FormatType::kR16_SSCALED:
+ return VK_FORMAT_R16_SSCALED;
+ case FormatType::kR16_UINT:
+ return VK_FORMAT_R16_UINT;
+ case FormatType::kR16_UNORM:
+ return VK_FORMAT_R16_UNORM;
+ case FormatType::kR16_USCALED:
+ return VK_FORMAT_R16_USCALED;
+ case FormatType::kR32G32B32A32_SFLOAT:
+ return VK_FORMAT_R32G32B32A32_SFLOAT;
+ case FormatType::kR32G32B32A32_SINT:
+ return VK_FORMAT_R32G32B32A32_SINT;
+ case FormatType::kR32G32B32A32_UINT:
+ return VK_FORMAT_R32G32B32A32_UINT;
+ case FormatType::kR32G32B32_SFLOAT:
+ return VK_FORMAT_R32G32B32_SFLOAT;
+ case FormatType::kR32G32B32_SINT:
+ return VK_FORMAT_R32G32B32_SINT;
+ case FormatType::kR32G32B32_UINT:
+ return VK_FORMAT_R32G32B32_UINT;
+ case FormatType::kR32G32_SFLOAT:
+ return VK_FORMAT_R32G32_SFLOAT;
+ case FormatType::kR32G32_SINT:
+ return VK_FORMAT_R32G32_SINT;
+ case FormatType::kR32G32_UINT:
+ return VK_FORMAT_R32G32_UINT;
+ case FormatType::kR32_SFLOAT:
+ return VK_FORMAT_R32_SFLOAT;
+ case FormatType::kR32_SINT:
+ return VK_FORMAT_R32_SINT;
+ case FormatType::kR32_UINT:
+ return VK_FORMAT_R32_UINT;
+ case FormatType::kR4G4B4A4_UNORM_PACK16:
+ return VK_FORMAT_R4G4B4A4_UNORM_PACK16;
+ case FormatType::kR4G4_UNORM_PACK8:
+ return VK_FORMAT_R4G4_UNORM_PACK8;
+ case FormatType::kR5G5B5A1_UNORM_PACK16:
+ return VK_FORMAT_R5G5B5A1_UNORM_PACK16;
+ case FormatType::kR5G6B5_UNORM_PACK16:
+ return VK_FORMAT_R5G6B5_UNORM_PACK16;
+ case FormatType::kR64G64B64A64_SFLOAT:
+ return VK_FORMAT_R64G64B64A64_SFLOAT;
+ case FormatType::kR64G64B64A64_SINT:
+ return VK_FORMAT_R64G64B64A64_SINT;
+ case FormatType::kR64G64B64A64_UINT:
+ return VK_FORMAT_R64G64B64A64_UINT;
+ case FormatType::kR64G64B64_SFLOAT:
+ return VK_FORMAT_R64G64B64_SFLOAT;
+ case FormatType::kR64G64B64_SINT:
+ return VK_FORMAT_R64G64B64_SINT;
+ case FormatType::kR64G64B64_UINT:
+ return VK_FORMAT_R64G64B64_UINT;
+ case FormatType::kR64G64_SFLOAT:
+ return VK_FORMAT_R64G64_SFLOAT;
+ case FormatType::kR64G64_SINT:
+ return VK_FORMAT_R64G64_SINT;
+ case FormatType::kR64G64_UINT:
+ return VK_FORMAT_R64G64_UINT;
+ case FormatType::kR64_SFLOAT:
+ return VK_FORMAT_R64_SFLOAT;
+ case FormatType::kR64_SINT:
+ return VK_FORMAT_R64_SINT;
+ case FormatType::kR64_UINT:
+ return VK_FORMAT_R64_UINT;
+ case FormatType::kR8G8B8A8_SINT:
+ return VK_FORMAT_R8G8B8A8_SINT;
+ case FormatType::kR8G8B8A8_SNORM:
+ return VK_FORMAT_R8G8B8A8_SNORM;
+ case FormatType::kR8G8B8A8_SRGB:
+ return VK_FORMAT_R8G8B8A8_SRGB;
+ case FormatType::kR8G8B8A8_SSCALED:
+ return VK_FORMAT_R8G8B8A8_SSCALED;
+ case FormatType::kR8G8B8A8_UINT:
+ return VK_FORMAT_R8G8B8A8_UINT;
+ case FormatType::kR8G8B8A8_UNORM:
+ return VK_FORMAT_R8G8B8A8_UNORM;
+ case FormatType::kR8G8B8A8_USCALED:
+ return VK_FORMAT_R8G8B8A8_USCALED;
+ case FormatType::kR8G8B8_SINT:
+ return VK_FORMAT_R8G8B8_SINT;
+ case FormatType::kR8G8B8_SNORM:
+ return VK_FORMAT_R8G8B8_SNORM;
+ case FormatType::kR8G8B8_SRGB:
+ return VK_FORMAT_R8G8B8_SRGB;
+ case FormatType::kR8G8B8_SSCALED:
+ return VK_FORMAT_R8G8B8_SSCALED;
+ case FormatType::kR8G8B8_UINT:
+ return VK_FORMAT_R8G8B8_UINT;
+ case FormatType::kR8G8B8_UNORM:
+ return VK_FORMAT_R8G8B8_UNORM;
+ case FormatType::kR8G8B8_USCALED:
+ return VK_FORMAT_R8G8B8_USCALED;
+ case FormatType::kR8G8_SINT:
+ return VK_FORMAT_R8G8_SINT;
+ case FormatType::kR8G8_SNORM:
+ return VK_FORMAT_R8G8_SNORM;
+ case FormatType::kR8G8_SRGB:
+ return VK_FORMAT_R8G8_SRGB;
+ case FormatType::kR8G8_SSCALED:
+ return VK_FORMAT_R8G8_SSCALED;
+ case FormatType::kR8G8_UINT:
+ return VK_FORMAT_R8G8_UINT;
+ case FormatType::kR8G8_UNORM:
+ return VK_FORMAT_R8G8_UNORM;
+ case FormatType::kR8G8_USCALED:
+ return VK_FORMAT_R8G8_USCALED;
+ case FormatType::kR8_SINT:
+ return VK_FORMAT_R8_SINT;
+ case FormatType::kR8_SNORM:
+ return VK_FORMAT_R8_SNORM;
+ case FormatType::kR8_SRGB:
+ return VK_FORMAT_R8_SRGB;
+ case FormatType::kR8_SSCALED:
+ return VK_FORMAT_R8_SSCALED;
+ case FormatType::kR8_UINT:
+ return VK_FORMAT_R8_UINT;
+ case FormatType::kR8_UNORM:
+ return VK_FORMAT_R8_UNORM;
+ case FormatType::kR8_USCALED:
+ return VK_FORMAT_R8_USCALED;
+ case FormatType::kS8_UINT:
+ return VK_FORMAT_S8_UINT;
+ case FormatType::kX8_D24_UNORM_PACK32:
+ return VK_FORMAT_X8_D24_UNORM_PACK32;
+ }
+ return VK_FORMAT_UNDEFINED;
+}
+
+uint32_t VkFormatToByteSize(VkFormat format) {
+ switch (format) {
+ case VK_FORMAT_R8G8B8A8_UNORM:
+ return 4;
+
+ // TODO(jaebaek): Handle all cases.
+ default:
+ break;
+ }
+ return 0;
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/format_data.h b/src/vulkan/format_data.h
new file mode 100644
index 0000000..c9dc44a
--- /dev/null
+++ b/src/vulkan/format_data.h
@@ -0,0 +1,30 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_FORMAT_DATA_H_
+#define SRC_VULKAN_FORMAT_DATA_H_
+
+#include "src/format_data.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+VkFormat ToVkFormat(FormatType type);
+uint32_t VkFormatToByteSize(VkFormat format);
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_FORMAT_DATA_H_
diff --git a/src/vulkan/frame_buffer.cc b/src/vulkan/frame_buffer.cc
new file mode 100644
index 0000000..ab36bbc
--- /dev/null
+++ b/src/vulkan/frame_buffer.cc
@@ -0,0 +1,83 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/frame_buffer.h"
+
+#include <limits>
+
+#include "src/make_unique.h"
+
+namespace amber {
+namespace vulkan {
+
+FrameBuffer::FrameBuffer(VkDevice device, uint32_t width, uint32_t height)
+ : device_(device), width_(width), height_(height) {}
+
+FrameBuffer::~FrameBuffer() = default;
+
+Result FrameBuffer::Initialize(
+ VkRenderPass render_pass,
+ VkFormat color_format,
+ VkFormat depth_format,
+ const VkPhysicalDeviceMemoryProperties& properties) {
+ std::vector<VkImageView> attachments;
+
+ if (color_format != VK_FORMAT_UNDEFINED) {
+ color_image_ = MakeUnique<Image>(device_, color_format, width_, height_,
+ depth_, properties);
+ Result r = color_image_->Initialize(VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
+ if (!r.IsSuccess())
+ return r;
+ attachments.push_back(color_image_->GetVkImageView());
+ }
+
+ if (depth_format != VK_FORMAT_UNDEFINED) {
+ depth_image_ = MakeUnique<Image>(device_, depth_format, width_, height_,
+ depth_, properties);
+ Result r =
+ depth_image_->Initialize(VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+ if (!r.IsSuccess())
+ return r;
+ attachments.push_back(depth_image_->GetVkImageView());
+ }
+
+ VkFramebufferCreateInfo frame_buffer_info = {};
+ frame_buffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ frame_buffer_info.renderPass = render_pass;
+ frame_buffer_info.attachmentCount = static_cast<uint32_t>(attachments.size());
+ frame_buffer_info.pAttachments = attachments.data();
+ frame_buffer_info.width = width_;
+ frame_buffer_info.height = height_;
+ frame_buffer_info.layers = 1;
+
+ if (vkCreateFramebuffer(device_, &frame_buffer_info, nullptr, &frame_) !=
+ VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreateFramebuffer Fail");
+ }
+
+ return {};
+}
+
+void FrameBuffer::Shutdown() {
+ vkDestroyFramebuffer(device_, frame_, nullptr);
+ if (color_image_)
+ color_image_->Shutdown();
+ if (depth_image_)
+ depth_image_->Shutdown();
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/frame_buffer.h b/src/vulkan/frame_buffer.h
new file mode 100644
index 0000000..0adf7ca
--- /dev/null
+++ b/src/vulkan/frame_buffer.h
@@ -0,0 +1,59 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_FRAME_BUFFER_H_
+#define SRC_VULKAN_FRAME_BUFFER_H_
+
+#include "src/vulkan/image.h"
+
+namespace amber {
+namespace vulkan {
+
+class FrameBuffer {
+ public:
+ FrameBuffer(VkDevice device, uint32_t width, uint32_t height);
+ ~FrameBuffer();
+
+ Result Initialize(VkRenderPass render_pass,
+ VkFormat color_format,
+ VkFormat depth_format,
+ const VkPhysicalDeviceMemoryProperties& properties);
+ void Shutdown();
+
+ VkFramebuffer GetFrameBuffer() const { return frame_; }
+ const void* GetColorBufferPtr() const {
+ return color_image_->HostAccessibleMemoryPtr();
+ }
+ VkImage GetColorImage() const { return color_image_->GetVkImage(); }
+ Result CopyColorImageToHost(VkCommandBuffer command) {
+ return color_image_->CopyToHost(command);
+ }
+
+ uint32_t GetWidth() const { return width_; }
+ uint32_t GetHeight() const { return height_; }
+
+ private:
+ VkDevice device_ = VK_NULL_HANDLE;
+ VkFramebuffer frame_ = VK_NULL_HANDLE;
+ std::unique_ptr<Image> color_image_;
+ std::unique_ptr<Image> depth_image_;
+ uint32_t width_ = 0;
+ uint32_t height_ = 0;
+ uint32_t depth_ = 1;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_FRAME_BUFFER_H_
diff --git a/src/vulkan/graphics_pipeline.cc b/src/vulkan/graphics_pipeline.cc
new file mode 100644
index 0000000..500d423
--- /dev/null
+++ b/src/vulkan/graphics_pipeline.cc
@@ -0,0 +1,590 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/graphics_pipeline.h"
+
+#include <cmath>
+
+#include "src/command.h"
+#include "src/make_unique.h"
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+const VkAttachmentDescription kDefaultAttachmentDesc = {
+ 0, /* flags */
+ VK_FORMAT_UNDEFINED, /* format */
+ VK_SAMPLE_COUNT_1_BIT, /* samples */
+ // TODO(jaebaek): Set up proper loadOp, StoreOp.
+ VK_ATTACHMENT_LOAD_OP_LOAD, /* loadOp */
+ VK_ATTACHMENT_STORE_OP_STORE, /* storeOp */
+ VK_ATTACHMENT_LOAD_OP_LOAD, /* stencilLoadOp */
+ VK_ATTACHMENT_STORE_OP_STORE, /* stencilStoreOp */
+ VK_IMAGE_LAYOUT_UNDEFINED, /* initialLayout */
+ VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, /* finalLayout */
+};
+
+const VkPipelineRasterizationStateCreateInfo kDefaultRasterizationInfo = {
+ VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, /* sType */
+ nullptr, /* pNext */
+ 0, /* flags */
+ VK_FALSE, /* depthClampEnable */
+ VK_FALSE, /* rasterizerDiscardEnable */
+ VK_POLYGON_MODE_FILL, /* polygonMode */
+ VK_CULL_MODE_NONE, /* cullMode */
+ VK_FRONT_FACE_CLOCKWISE, /* frontFace */
+ VK_FALSE, /* depthBiasEnable */
+ 0, /* depthBiasConstantFactor */
+ 0, /* depthBiasClamp */
+ 0, /* depthBiasSlopeFactor */
+ 0, /* lineWidth */
+};
+
+const VkSampleMask kSampleMask = ~0U;
+
+const VkPipelineMultisampleStateCreateInfo kMultisampleInfo = {
+ VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, /* sType */
+ nullptr, /* pNext */
+ 0, /* flags */
+ kDefaultAttachmentDesc.samples, /* rasterizationSamples */
+ VK_FALSE, /* sampleShadingEnable */
+ 0, /* minSampleShading */
+ &kSampleMask, /* pSampleMask */
+ VK_FALSE, /* alphaToCoverageEnable */
+ VK_FALSE, /* alphaToOneEnable */
+};
+
+const float kEpsilon = 0.002f;
+
+bool IsFloatPixelEqualInt(float pixel, uint8_t expected) {
+ // TODO(jaebaek): Change kEpsilon to tolerance.
+ return std::fabs(pixel - static_cast<float>(expected) / 255.0f) < kEpsilon;
+}
+
+} // namespace
+
+GraphicsPipeline::GraphicsPipeline(
+ PipelineType type,
+ VkDevice device,
+ const VkPhysicalDeviceMemoryProperties& properties,
+ VkFormat color_format,
+ VkFormat depth_stencil_format,
+ std::vector<VkPipelineShaderStageCreateInfo> shader_stage_info)
+ : Pipeline(type, device, properties),
+ color_format_(color_format),
+ depth_stencil_format_(depth_stencil_format),
+ shader_stage_info_(shader_stage_info) {}
+
+GraphicsPipeline::~GraphicsPipeline() = default;
+
+Result GraphicsPipeline::CreateRenderPass() {
+ VkSubpassDescription subpass_desc = {};
+ subpass_desc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+
+ std::vector<VkAttachmentDescription> attachment_desc;
+
+ VkAttachmentReference color_refer = {};
+ VkAttachmentReference depth_refer = {};
+
+ if (color_format_ != VK_FORMAT_UNDEFINED) {
+ attachment_desc.push_back(kDefaultAttachmentDesc);
+ attachment_desc.back().format = color_format_;
+
+ color_refer.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
+ color_refer.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ subpass_desc.colorAttachmentCount = 1;
+ subpass_desc.pColorAttachments = &color_refer;
+ }
+
+ if (depth_stencil_format_ != VK_FORMAT_UNDEFINED) {
+ attachment_desc.push_back(kDefaultAttachmentDesc);
+ attachment_desc.back().format = depth_stencil_format_;
+
+ depth_refer.attachment = static_cast<uint32_t>(attachment_desc.size() - 1);
+ depth_refer.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ subpass_desc.pDepthStencilAttachment = &depth_refer;
+ }
+
+ VkRenderPassCreateInfo render_pass_info = {};
+ render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ render_pass_info.attachmentCount =
+ static_cast<uint32_t>(attachment_desc.size());
+ render_pass_info.pAttachments = attachment_desc.data();
+ render_pass_info.subpassCount = 1;
+ render_pass_info.pSubpasses = &subpass_desc;
+
+ if (vkCreateRenderPass(device_, &render_pass_info, nullptr, &render_pass_) !=
+ VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreateRenderPass Fail");
+ }
+
+ return {};
+}
+
+VkPipelineDepthStencilStateCreateInfo
+GraphicsPipeline::GetPipelineDepthStencilInfo() {
+ VkPipelineDepthStencilStateCreateInfo depthstencil_info = {};
+ // TODO(jaebaek): Depth/stencil test setup should be come from the
+ // PipelineData.
+ depthstencil_info.depthTestEnable = VK_TRUE;
+ depthstencil_info.depthWriteEnable = VK_TRUE;
+ depthstencil_info.depthCompareOp = VK_COMPARE_OP_LESS;
+ depthstencil_info.depthBoundsTestEnable = VK_FALSE;
+ depthstencil_info.stencilTestEnable = VK_FALSE;
+ return depthstencil_info;
+}
+
+VkPipelineColorBlendAttachmentState
+GraphicsPipeline::GetPipelineColorBlendAttachmentState() {
+ VkPipelineColorBlendAttachmentState colorblend_attachment = {};
+ // TODO(jaebaek): Update blend state should be come from the PipelineData.
+ colorblend_attachment.blendEnable = VK_FALSE;
+ colorblend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+ colorblend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO;
+ colorblend_attachment.colorBlendOp = VK_BLEND_OP_ADD;
+ colorblend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+ colorblend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+ colorblend_attachment.alphaBlendOp = VK_BLEND_OP_ADD;
+ colorblend_attachment.colorWriteMask =
+ VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
+ VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
+ return colorblend_attachment;
+}
+
+Result GraphicsPipeline::CreateVkGraphicsPipeline() {
+ if (pipeline_ != VK_NULL_HANDLE)
+ return Result("Vulkan::Pipeline already created");
+
+ Result r = CreatePipelineLayout();
+ if (!r.IsSuccess())
+ return r;
+
+ VkPipelineVertexInputStateCreateInfo vertex_input_info = {};
+ vertex_input_info.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+ vertex_input_info.vertexBindingDescriptionCount = 1;
+
+ VkVertexInputBindingDescription vertex_binding_desc = {};
+ if (vertex_buffer_) {
+ vertex_binding_desc = vertex_buffer_->GetVertexInputBinding();
+ auto vertex_attr_desc = vertex_buffer_->GetVertexInputAttr();
+
+ vertex_input_info.pVertexBindingDescriptions = &vertex_binding_desc;
+ vertex_input_info.vertexAttributeDescriptionCount =
+ static_cast<uint32_t>(vertex_attr_desc.size());
+ vertex_input_info.pVertexAttributeDescriptions = vertex_attr_desc.data();
+ } else {
+ vertex_binding_desc.binding = 0;
+ vertex_binding_desc.stride = 0;
+ vertex_binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+
+ vertex_input_info.pVertexBindingDescriptions = &vertex_binding_desc;
+ vertex_input_info.vertexAttributeDescriptionCount = 0;
+ vertex_input_info.pVertexAttributeDescriptions = nullptr;
+ }
+
+ VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {};
+ input_assembly_info.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+ // TODO(jaebaek): Handle the given index if exists.
+ input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
+ input_assembly_info.primitiveRestartEnable = VK_FALSE;
+
+ VkViewport viewport = {0,
+ 0,
+ static_cast<float>(frame_->GetWidth()),
+ static_cast<float>(frame_->GetHeight()),
+ 0,
+ 1};
+
+ VkRect2D scissor = {{0, 0}, {frame_->GetWidth(), frame_->GetHeight()}};
+
+ VkPipelineViewportStateCreateInfo viewport_info = {};
+ viewport_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ viewport_info.viewportCount = 1;
+ viewport_info.pViewports = &viewport;
+ viewport_info.scissorCount = 1;
+ viewport_info.pScissors = &scissor;
+
+ VkGraphicsPipelineCreateInfo pipeline_info = {};
+ pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+ pipeline_info.stageCount = static_cast<uint32_t>(shader_stage_info_.size());
+ pipeline_info.pStages = shader_stage_info_.data();
+ pipeline_info.pVertexInputState = &vertex_input_info;
+ pipeline_info.pInputAssemblyState = &input_assembly_info;
+ pipeline_info.pViewportState = &viewport_info;
+ pipeline_info.pRasterizationState = &kDefaultRasterizationInfo;
+ pipeline_info.pMultisampleState = &kMultisampleInfo;
+
+ VkPipelineDepthStencilStateCreateInfo depthstencil_info;
+ if (depth_stencil_format_ != VK_FORMAT_UNDEFINED) {
+ depthstencil_info = GetPipelineDepthStencilInfo();
+ pipeline_info.pDepthStencilState = &depthstencil_info;
+ }
+
+ VkPipelineColorBlendStateCreateInfo colorblend_info = {};
+ VkPipelineColorBlendAttachmentState colorblend_attachment;
+ if (color_format_ != VK_FORMAT_UNDEFINED) {
+ colorblend_attachment = GetPipelineColorBlendAttachmentState();
+
+ colorblend_info.sType =
+ VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ colorblend_info.logicOpEnable = VK_FALSE;
+ colorblend_info.logicOp = VK_LOGIC_OP_COPY;
+ colorblend_info.attachmentCount = 1;
+ colorblend_info.pAttachments = &colorblend_attachment;
+ pipeline_info.pColorBlendState = &colorblend_info;
+ }
+
+ pipeline_info.layout = pipeline_layout_;
+ pipeline_info.renderPass = render_pass_;
+ pipeline_info.subpass = 0;
+
+ if (vkCreateGraphicsPipelines(device_, VK_NULL_HANDLE, 1, &pipeline_info,
+ nullptr, &pipeline_) != VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreateGraphicsPipelines Fail");
+ }
+
+ return {};
+}
+
+Result GraphicsPipeline::Initialize(uint32_t width,
+ uint32_t height,
+ VkCommandPool pool,
+ VkQueue queue) {
+ Result r = Pipeline::InitializeCommandBuffer(pool, queue);
+ if (!r.IsSuccess())
+ return r;
+
+ r = CreateRenderPass();
+ if (!r.IsSuccess())
+ return r;
+
+ frame_ = MakeUnique<FrameBuffer>(device_, width, height);
+ r = frame_->Initialize(render_pass_, color_format_, depth_stencil_format_,
+ memory_properties_);
+ if (!r.IsSuccess())
+ return r;
+
+ frame_width_ = width;
+ frame_height_ = height;
+
+ return {};
+}
+
+void GraphicsPipeline::SetBuffer(BufferType type,
+ uint8_t location,
+ const Format& format,
+ const std::vector<Value>& values) {
+ // TODO(jaebaek): Handle indices data.
+ if (type != BufferType::kVertexData)
+ return;
+
+ if (!vertex_buffer_)
+ vertex_buffer_ = MakeUnique<VertexBuffer>(device_);
+
+ vertex_buffer_->SetData(location, format, values);
+}
+
+Result GraphicsPipeline::SendBufferDataIfNeeded() {
+ if (!vertex_buffer_)
+ return {};
+
+ if (vertex_buffer_->VertexDataSent())
+ return {};
+
+ Result r = command_->BeginIfNotInRecording();
+ if (!r.IsSuccess())
+ return r;
+
+ DeactivateRenderPassIfNeeded();
+
+ // TODO(jaebaek): Send indices data too.
+ return vertex_buffer_->SendVertexData(command_->GetCommandBuffer(),
+ memory_properties_);
+}
+
+void GraphicsPipeline::ActivateRenderPassIfNeeded() {
+ if (render_pass_state_ == RenderPassState::kActive)
+ return;
+
+ VkRenderPassBeginInfo render_begin_info = {};
+ render_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ render_begin_info.renderPass = render_pass_;
+ render_begin_info.framebuffer = frame_->GetFrameBuffer();
+ render_begin_info.renderArea = {{0, 0}, {frame_width_, frame_height_}};
+ vkCmdBeginRenderPass(command_->GetCommandBuffer(), &render_begin_info,
+ VK_SUBPASS_CONTENTS_INLINE);
+ render_pass_state_ = RenderPassState::kActive;
+}
+
+void GraphicsPipeline::DeactivateRenderPassIfNeeded() {
+ if (render_pass_state_ == RenderPassState::kInactive)
+ return;
+
+ vkCmdEndRenderPass(command_->GetCommandBuffer());
+ render_pass_state_ = RenderPassState::kInactive;
+}
+
+Result GraphicsPipeline::SetClearColor(float r, float g, float b, float a) {
+ if (color_format_ == VK_FORMAT_UNDEFINED) {
+ return Result(
+ "Vulkan::ClearColorCommand No Color Buffer for FrameBuffer Exists");
+ }
+
+ clear_color_r_ = r;
+ clear_color_g_ = g;
+ clear_color_b_ = b;
+ clear_color_a_ = a;
+ return {};
+}
+
+Result GraphicsPipeline::SetClearStencil(uint32_t stencil) {
+ if (depth_stencil_format_ == VK_FORMAT_UNDEFINED) {
+ return Result(
+ "Vulkan::ClearStencilCommand No DepthStencil Buffer for FrameBuffer "
+ "Exists");
+ }
+
+ clear_stencil_ = stencil;
+ return {};
+}
+
+Result GraphicsPipeline::SetClearDepth(float depth) {
+ if (depth_stencil_format_ == VK_FORMAT_UNDEFINED) {
+ return Result(
+ "Vulkan::ClearStencilCommand No DepthStencil Buffer for FrameBuffer "
+ "Exists");
+ }
+
+ clear_depth_ = depth;
+ return {};
+}
+
+Result GraphicsPipeline::Clear() {
+ if (color_format_ == VK_FORMAT_UNDEFINED &&
+ depth_stencil_format_ == VK_FORMAT_UNDEFINED) {
+ return Result(
+ "Vulkan::ClearColorCommand No Color nor DepthStencil Buffer for "
+ "FrameBuffer Exists");
+ }
+
+ if (color_format_ != VK_FORMAT_UNDEFINED) {
+ VkClearValue clear_value;
+ clear_value.color = {
+ {clear_color_r_, clear_color_g_, clear_color_b_, clear_color_a_}};
+ Result r = ClearBuffer(clear_value, VK_IMAGE_ASPECT_COLOR_BIT);
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ if (depth_stencil_format_ == VK_FORMAT_UNDEFINED)
+ return {};
+
+ VkClearValue clear_value;
+ clear_value.depthStencil = {clear_depth_, clear_stencil_};
+ return ClearBuffer(clear_value,
+ VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
+}
+
+Result GraphicsPipeline::ClearBuffer(const VkClearValue& clear_value,
+ VkImageAspectFlags aspect) {
+ Result r = command_->BeginIfNotInRecording();
+ if (!r.IsSuccess())
+ return r;
+
+ // TODO(jaebaek): When multiple clear and draw commands exist, handle
+ // begin/end render pass properly.
+ ActivateRenderPassIfNeeded();
+
+ VkClearAttachment clear_attachment = {};
+ clear_attachment.aspectMask = aspect;
+ clear_attachment.colorAttachment = 0;
+ clear_attachment.clearValue = clear_value;
+
+ VkClearRect clear_rect;
+ clear_rect.rect = {{0, 0}, {frame_width_, frame_height_}};
+ clear_rect.baseArrayLayer = 0;
+ clear_rect.layerCount = 1;
+
+ vkCmdClearAttachments(command_->GetCommandBuffer(), 1, &clear_attachment, 1,
+ &clear_rect);
+
+ return {};
+}
+
+Result GraphicsPipeline::Draw() {
+ // TODO(jaebaek): Handle primitive topology.
+ if (pipeline_ == VK_NULL_HANDLE) {
+ Result r = CreateVkGraphicsPipeline();
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ Result r = SendBufferDataIfNeeded();
+ if (!r.IsSuccess())
+ return r;
+
+ r = command_->BeginIfNotInRecording();
+ if (!r.IsSuccess())
+ return r;
+
+ ActivateRenderPassIfNeeded();
+
+ vkCmdBindPipeline(command_->GetCommandBuffer(),
+ VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_);
+
+ uint32_t vertex_count = 0;
+ uint32_t instance_count = 0;
+ if (vertex_buffer_) {
+ vertex_buffer_->BindToCommandBuffer(command_->GetCommandBuffer());
+ vertex_count = static_cast<uint32_t>(vertex_buffer_->GetVertexCount());
+ instance_count = 1;
+ }
+
+ vkCmdDraw(command_->GetCommandBuffer(), vertex_count, instance_count, 0, 0);
+
+ return {};
+}
+
+Result GraphicsPipeline::SubmitProbeCommand() {
+ Result r = command_->BeginIfNotInRecording();
+ if (!r.IsSuccess())
+ return r;
+
+ ActivateRenderPassIfNeeded();
+ DeactivateRenderPassIfNeeded();
+
+ r = frame_->CopyColorImageToHost(command_->GetCommandBuffer());
+ if (!r.IsSuccess())
+ return r;
+
+ r = command_->End();
+ if (!r.IsSuccess())
+ return r;
+
+ return command_->SubmitAndReset();
+}
+
+Result GraphicsPipeline::VerifyPixels(const uint32_t x,
+ const uint32_t y,
+ const uint32_t width,
+ const uint32_t height,
+ const ProbeCommand* command) {
+ const uint32_t stride = VkFormatToByteSize(color_format_);
+
+ // TODO(jaebaek): Support all VkFormat
+ const uint8_t* ptr = static_cast<const uint8_t*>(frame_->GetColorBufferPtr());
+ uint32_t count_of_invalid_pixels = 0;
+ uint32_t first_invalid_i = 0;
+ uint32_t first_invalid_j = 0;
+ for (uint32_t j = 0; j < height; ++j) {
+ const uint8_t* p = ptr + stride * frame_->GetWidth() * (j + y) + stride * x;
+ for (uint32_t i = 0; i < width; ++i) {
+ // TODO(jaebaek): Get actual pixel values based on frame buffer formats.
+ if (!IsFloatPixelEqualInt(command->GetR(), p[stride * i]) ||
+ !IsFloatPixelEqualInt(command->GetG(), p[stride * i + 1]) ||
+ !IsFloatPixelEqualInt(command->GetB(), p[stride * i + 2]) ||
+ (command->IsRGBA() &&
+ !IsFloatPixelEqualInt(command->GetA(), p[stride * i + 3]))) {
+ if (!count_of_invalid_pixels) {
+ first_invalid_i = i;
+ first_invalid_j = j;
+ }
+ ++count_of_invalid_pixels;
+ }
+ }
+ }
+
+ if (count_of_invalid_pixels) {
+ const uint8_t* p =
+ ptr + stride * frame_->GetWidth() * (first_invalid_j + y) + stride * x;
+ return Result(
+ "Probe failed at: " + std::to_string(first_invalid_i + x) + ", " +
+ std::to_string(first_invalid_j + y) + "\n" +
+ " Expected RGBA: " + std::to_string(command->GetR() * 255) + ", " +
+ std::to_string(command->GetG() * 255) + ", " +
+ std::to_string(command->GetB() * 255) +
+ (command->IsRGBA() ? ", " + std::to_string(command->GetA() * 255) +
+ "\n Actual RGBA: "
+ : "\n Actual RGB: ") +
+ std::to_string(static_cast<int>(p[stride * first_invalid_i])) + ", " +
+ std::to_string(static_cast<int>(p[stride * first_invalid_i + 1])) +
+ ", " +
+ std::to_string(static_cast<int>(p[stride * first_invalid_i + 2])) +
+ (command->IsRGBA() ? ", " + std::to_string(static_cast<int>(
+ p[stride * first_invalid_i + 3]))
+ : "") +
+ "\n" + "Probe failed in " + std::to_string(count_of_invalid_pixels) +
+ " pixels");
+ }
+
+ return {};
+}
+
+Result GraphicsPipeline::Probe(const ProbeCommand* command) {
+ uint32_t x = 0;
+ uint32_t y = 0;
+ uint32_t width = 0;
+ uint32_t height = 0;
+ const uint32_t frame_width = frame_->GetWidth();
+ const uint32_t frame_height = frame_->GetHeight();
+
+ if (command->IsWholeWindow()) {
+ width = frame_width;
+ height = frame_height;
+ } else if (command->IsRelative()) {
+ x = static_cast<uint32_t>(frame_width * command->GetX());
+ y = static_cast<uint32_t>(frame_height * command->GetY());
+ width = static_cast<uint32_t>(frame_width * command->GetWidth());
+ height = static_cast<uint32_t>(frame_height * command->GetHeight());
+ } else {
+ x = static_cast<uint32_t>(command->GetX());
+ y = static_cast<uint32_t>(command->GetY());
+ width = static_cast<uint32_t>(command->GetWidth());
+ height = static_cast<uint32_t>(command->GetHeight());
+ }
+
+ if (x + width > frame_width || y + height > frame_height) {
+ return Result(
+ "Vulkan::Probe Position(" + std::to_string(x + width - 1) + ", " +
+ std::to_string(y + height - 1) + ") is out of framebuffer scope (" +
+ std::to_string(frame_width) + "," + std::to_string(frame_height) + ")");
+ }
+
+ Result r = SubmitProbeCommand();
+ if (!r.IsSuccess())
+ return r;
+
+ return VerifyPixels(x, y, width, height, command);
+}
+
+void GraphicsPipeline::Shutdown() {
+ DeactivateRenderPassIfNeeded();
+
+ Result r = command_->End();
+ if (r.IsSuccess())
+ command_->SubmitAndReset();
+
+ Pipeline::Shutdown();
+ frame_->Shutdown();
+ vkDestroyRenderPass(device_, render_pass_, nullptr);
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/graphics_pipeline.h b/src/vulkan/graphics_pipeline.h
new file mode 100644
index 0000000..9608dc9
--- /dev/null
+++ b/src/vulkan/graphics_pipeline.h
@@ -0,0 +1,121 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_GRAPHICS_PIPELINE_H_
+#define SRC_VULKAN_GRAPHICS_PIPELINE_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/buffer_data.h"
+#include "src/format.h"
+#include "src/value.h"
+#include "src/vulkan/frame_buffer.h"
+#include "src/vulkan/pipeline.h"
+#include "src/vulkan/vertex_buffer.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+
+class ProbeCommand;
+
+namespace vulkan {
+
+class GraphicsPipeline : public Pipeline {
+ public:
+ GraphicsPipeline(PipelineType type,
+ VkDevice device,
+ const VkPhysicalDeviceMemoryProperties& properties,
+ VkFormat color_format,
+ VkFormat depth_stencil_format,
+ std::vector<VkPipelineShaderStageCreateInfo>);
+ ~GraphicsPipeline() override;
+
+ Result Initialize(uint32_t width,
+ uint32_t height,
+ VkCommandPool pool,
+ VkQueue queue);
+ void Shutdown() override;
+
+ void SetBuffer(BufferType type,
+ uint8_t location,
+ const Format& format,
+ const std::vector<Value>& values);
+
+ Result Clear();
+ Result ClearBuffer(const VkClearValue& clear_value,
+ VkImageAspectFlags aspect);
+ Result Probe(const ProbeCommand*);
+
+ VkFormat GetColorFormat() const { return color_format_; }
+ VkFormat GetDepthStencilFormat() const { return depth_stencil_format_; }
+
+ Result SetClearColor(float r, float g, float b, float a);
+ Result SetClearStencil(uint32_t stencil);
+ Result SetClearDepth(float depth);
+
+ Result Draw();
+
+ private:
+ enum class RenderPassState : uint8_t {
+ kActive = 0,
+ kInactive,
+ };
+
+ Result CreateVkGraphicsPipeline();
+
+ Result CreateRenderPass();
+ void ActivateRenderPassIfNeeded();
+ void DeactivateRenderPassIfNeeded();
+
+ Result SendBufferDataIfNeeded();
+
+ // TODO(jaebaek): Implement image/ssbo probe.
+ Result SubmitProbeCommand();
+ Result VerifyPixels(const uint32_t x,
+ const uint32_t y,
+ const uint32_t width,
+ const uint32_t height,
+ const ProbeCommand* command);
+
+ VkPipelineDepthStencilStateCreateInfo GetPipelineDepthStencilInfo();
+ VkPipelineColorBlendAttachmentState GetPipelineColorBlendAttachmentState();
+
+ VkRenderPass render_pass_ = VK_NULL_HANDLE;
+ RenderPassState render_pass_state_ = RenderPassState::kInactive;
+
+ std::unique_ptr<FrameBuffer> frame_;
+ VkFormat color_format_;
+ VkFormat depth_stencil_format_;
+
+ std::vector<VkPipelineShaderStageCreateInfo> shader_stage_info_;
+
+ uint32_t frame_width_ = 0;
+ uint32_t frame_height_ = 0;
+
+ float clear_color_r_ = 0;
+ float clear_color_g_ = 0;
+ float clear_color_b_ = 0;
+ float clear_color_a_ = 0;
+ uint32_t clear_stencil_ = 0;
+ float clear_depth_ = 1.0f;
+
+ std::unique_ptr<VertexBuffer> vertex_buffer_;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_GRAPHICS_PIPELINE_H_
diff --git a/src/vulkan/image.cc b/src/vulkan/image.cc
new file mode 100644
index 0000000..4722187
--- /dev/null
+++ b/src/vulkan/image.cc
@@ -0,0 +1,151 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/image.h"
+
+#include <limits>
+
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+namespace {
+
+const VkImageCreateInfo kDefaultImageInfo = {
+ VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, /* sType */
+ nullptr, /* pNext */
+ 0, /* flags */
+ VK_IMAGE_TYPE_2D, /* imageType */
+ VK_FORMAT_R8G8B8A8_UNORM, /* format */
+ {250, 250, 1}, /* extent */
+ 1, /* mipLevels */
+ 1, /* arrayLayers */
+ VK_SAMPLE_COUNT_1_BIT, /* samples */
+ VK_IMAGE_TILING_OPTIMAL, /* tiling */
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, /* usage */
+ VK_SHARING_MODE_EXCLUSIVE, /* sharingMode */
+ 0, /* queueFamilyIndexCount */
+ nullptr, /* pQueueFamilyIndices */
+ VK_IMAGE_LAYOUT_UNDEFINED, /* initialLayout */
+};
+
+} // namespace
+
+Image::Image(VkDevice device,
+ VkFormat format,
+ uint32_t x,
+ uint32_t y,
+ uint32_t z,
+ const VkPhysicalDeviceMemoryProperties& properties)
+ : Resource(device, x * y * z * VkFormatToByteSize(format), properties),
+ image_info_(kDefaultImageInfo) {
+ image_info_.format = format;
+ image_info_.extent = {x, y, z};
+}
+
+Image::~Image() = default;
+
+Result Image::Initialize(VkImageUsageFlags usage) {
+ if (image_ != VK_NULL_HANDLE)
+ return Result("Vulkan::Image was already initalized");
+
+ image_info_.usage = usage;
+
+ if (vkCreateImage(GetDevice(), &image_info_, nullptr, &image_) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkCreateImage Fail");
+
+ AllocateResult allocate_result = AllocateAndBindMemoryToVkImage(
+ image_, &memory_, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, false);
+ if (!allocate_result.r.IsSuccess())
+ return allocate_result.r;
+
+ Result r = CreateVkImageView();
+ if (!r.IsSuccess())
+ return r;
+
+ if (CheckMemoryHostAccessible(allocate_result.memory_type_index)) {
+ is_image_host_accessible_ = true;
+ return MapMemory(memory_);
+ }
+
+ is_image_host_accessible_ = false;
+ return Resource::Initialize();
+}
+
+Result Image::CreateVkImageView() {
+ VkImageViewCreateInfo image_view_info = {};
+ image_view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ image_view_info.image = image_;
+ // TODO(jaebaek): Set .viewType correctly
+ image_view_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ image_view_info.format = image_info_.format;
+ image_view_info.components = {
+ VK_COMPONENT_SWIZZLE_R,
+ VK_COMPONENT_SWIZZLE_G,
+ VK_COMPONENT_SWIZZLE_B,
+ VK_COMPONENT_SWIZZLE_A,
+ };
+ image_view_info.subresourceRange = {
+ VK_IMAGE_ASPECT_COLOR_BIT, /* aspectMask */
+ 0, /* baseMipLevel */
+ 1, /* levelCount */
+ 0, /* baseArrayLayer */
+ 1, /* layerCount */
+ };
+
+ if (vkCreateImageView(GetDevice(), &image_view_info, nullptr, &view_) !=
+ VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreateImageView Fail");
+ }
+
+ return {};
+}
+
+void Image::Shutdown() {
+ vkDestroyImageView(GetDevice(), view_, nullptr);
+ vkDestroyImage(GetDevice(), image_, nullptr);
+ vkFreeMemory(GetDevice(), memory_, nullptr);
+
+ view_ = VK_NULL_HANDLE;
+ image_ = VK_NULL_HANDLE;
+ memory_ = VK_NULL_HANDLE;
+}
+
+Result Image::CopyToHost(VkCommandBuffer command) {
+ if (is_image_host_accessible_)
+ return {};
+
+ VkBufferImageCopy copy_region = {};
+ copy_region.bufferOffset = 0;
+ copy_region.bufferRowLength = 0;
+ copy_region.bufferImageHeight = 0;
+ copy_region.imageSubresource = {
+ VK_IMAGE_ASPECT_COLOR_BIT, /* aspectMask */
+ 0, /* mipLevel */
+ 0, /* baseArrayLayer */
+ 1, /* layerCount */
+ };
+ copy_region.imageOffset = {0, 0, 0};
+ copy_region.imageExtent = {image_info_.extent.width,
+ image_info_.extent.height, 1};
+
+ vkCmdCopyImageToBuffer(command, image_, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ GetHostAccessibleBuffer(), 1, &copy_region);
+
+ return {};
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/image.h b/src/vulkan/image.h
new file mode 100644
index 0000000..c946af4
--- /dev/null
+++ b/src/vulkan/image.h
@@ -0,0 +1,68 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_IMAGE_H_
+#define SRC_VULKAN_IMAGE_H_
+
+#include "amber/result.h"
+#include "src/vulkan/resource.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class Image : public Resource {
+ public:
+ Image(VkDevice device,
+ VkFormat format,
+ uint32_t x,
+ uint32_t y,
+ uint32_t z,
+ const VkPhysicalDeviceMemoryProperties& properties);
+ ~Image() override;
+
+ Result Initialize(VkImageUsageFlags usage);
+ VkImage GetVkImage() const { return image_; }
+ VkImageView GetVkImageView() const { return view_; }
+
+ // TODO(jaebaek): Determine copy all or partial data
+ Result CopyToHost(VkCommandBuffer command);
+
+ // TODO(jaebaek): Implement CopyToDevice
+
+ // Resource
+ VkDeviceMemory GetHostAccessMemory() const override {
+ if (is_image_host_accessible_)
+ return memory_;
+
+ return Resource::GetHostAccessMemory();
+ }
+
+ void Shutdown() override;
+
+ private:
+ Result CreateVkImageView();
+
+ VkImageCreateInfo image_info_;
+
+ VkImage image_ = VK_NULL_HANDLE;
+ VkImageView view_ = VK_NULL_HANDLE;
+ VkDeviceMemory memory_ = VK_NULL_HANDLE;
+ bool is_image_host_accessible_ = false;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_IMAGE_H_
diff --git a/src/vulkan/pipeline.cc b/src/vulkan/pipeline.cc
new file mode 100644
index 0000000..e16c277
--- /dev/null
+++ b/src/vulkan/pipeline.cc
@@ -0,0 +1,68 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/pipeline.h"
+
+#include <limits>
+
+#include "src/command.h"
+#include "src/make_unique.h"
+#include "src/vulkan/graphics_pipeline.h"
+
+namespace amber {
+namespace vulkan {
+
+Pipeline::Pipeline(PipelineType type,
+ VkDevice device,
+ const VkPhysicalDeviceMemoryProperties& properties)
+ : device_(device), memory_properties_(properties), pipeline_type_(type) {}
+
+Pipeline::~Pipeline() = default;
+
+GraphicsPipeline* Pipeline::AsGraphics() {
+ return static_cast<GraphicsPipeline*>(this);
+}
+
+Result Pipeline::InitializeCommandBuffer(VkCommandPool pool, VkQueue queue) {
+ command_ = MakeUnique<CommandBuffer>(device_, pool, queue);
+ Result r = command_->Initialize();
+ if (!r.IsSuccess())
+ return r;
+
+ return {};
+}
+
+void Pipeline::Shutdown() {
+ // TODO(jaebaek): destroy pipeline_cache_ and pipeline_
+ command_->Shutdown();
+ vkDestroyPipelineLayout(device_, pipeline_layout_, nullptr);
+}
+
+Result Pipeline::CreatePipelineLayout() {
+ VkPipelineLayoutCreateInfo pipeline_layout_info = {};
+ pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+ pipeline_layout_info.setLayoutCount = 0;
+ pipeline_layout_info.pSetLayouts = nullptr;
+ // TODO(jaebaek): Push constant for pipeline_layout_info.
+
+ if (vkCreatePipelineLayout(device_, &pipeline_layout_info, nullptr,
+ &pipeline_layout_) != VK_SUCCESS) {
+ return Result("Vulkan::Calling vkCreatePipelineLayout Fail");
+ }
+
+ return {};
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/pipeline.h b/src/vulkan/pipeline.h
new file mode 100644
index 0000000..38a234a
--- /dev/null
+++ b/src/vulkan/pipeline.h
@@ -0,0 +1,67 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_PIPELINE_H_
+#define SRC_VULKAN_PIPELINE_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/engine.h"
+#include "src/vulkan/command.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class GraphicsPipeline;
+
+class Pipeline {
+ public:
+ virtual ~Pipeline();
+
+ bool IsGraphics() const { return pipeline_type_ == PipelineType::kGraphics; }
+ bool IsCompute() const { return pipeline_type_ == PipelineType::kCompute; }
+
+ GraphicsPipeline* AsGraphics();
+
+ virtual void Shutdown();
+
+ protected:
+ Pipeline(PipelineType type,
+ VkDevice device,
+ const VkPhysicalDeviceMemoryProperties& properties);
+ Result InitializeCommandBuffer(VkCommandPool pool, VkQueue queue);
+
+ Result CreatePipelineLayout();
+
+ VkPipelineCache pipeline_cache_ = VK_NULL_HANDLE;
+ VkPipeline pipeline_ = VK_NULL_HANDLE;
+ VkPipelineLayout pipeline_layout_ = VK_NULL_HANDLE;
+
+ std::vector<VkDescriptorSetLayout> descriptor_set_layout_;
+
+ VkDevice device_ = VK_NULL_HANDLE;
+ VkPhysicalDeviceMemoryProperties memory_properties_;
+ std::unique_ptr<CommandBuffer> command_;
+
+ private:
+ PipelineType pipeline_type_;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_PIPELINE_H_
diff --git a/src/vulkan/resource.cc b/src/vulkan/resource.cc
new file mode 100644
index 0000000..69a21a5
--- /dev/null
+++ b/src/vulkan/resource.cc
@@ -0,0 +1,209 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/resource.h"
+
+#include <limits>
+
+#include "src/make_unique.h"
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+
+Resource::Resource(VkDevice device,
+ size_t size,
+ const VkPhysicalDeviceMemoryProperties& properties)
+ : device_(device), size_(size), physical_memory_properties_(properties) {}
+
+Resource::~Resource() = default;
+
+void Resource::Shutdown() {
+ UnMapMemory(host_accessible_memory_);
+ vkDestroyBuffer(device_, host_accessible_buffer_, nullptr);
+ vkFreeMemory(device_, host_accessible_memory_, nullptr);
+}
+
+Result Resource::Initialize() {
+ Result r = CreateVkBuffer(
+ &host_accessible_buffer_,
+ VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+ if (!r.IsSuccess())
+ return r;
+
+ AllocateResult allocate_result = AllocateAndBindMemoryToVkBuffer(
+ host_accessible_buffer_, &host_accessible_memory_,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
+ VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ true);
+ if (!allocate_result.r.IsSuccess())
+ return allocate_result.r;
+
+ return MapMemory(host_accessible_memory_);
+}
+
+Result Resource::CreateVkBuffer(VkBuffer* buffer, VkBufferUsageFlags usage) {
+ if (buffer == nullptr)
+ return Result("Vulkan::Given VkBuffer pointer is nullptr");
+
+ VkBufferCreateInfo buffer_info = {};
+ buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ buffer_info.size = size_;
+ buffer_info.usage = usage;
+
+ if (vkCreateBuffer(device_, &buffer_info, nullptr, buffer) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkCreateBuffer Fail");
+
+ return {};
+}
+
+uint32_t Resource::ChooseMemory(uint32_t memory_type_bits,
+ VkMemoryPropertyFlags flags,
+ bool force_flags) {
+ // Based on Vulkan spec about VkMemoryRequirements, N th bit of
+ // |memory_type_bits| is 1 where N can be the proper memory type index.
+ // This code is looking for the first non-zero bit whose memory type
+ // VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT property. If not exists,
+ // it returns the first non-zero bit.
+ uint32_t first_non_zero = std::numeric_limits<uint32_t>::max();
+ uint32_t memory_type_index = 0;
+ while (memory_type_bits) {
+ if (memory_type_bits % 2) {
+ if (first_non_zero == std::numeric_limits<uint32_t>::max())
+ first_non_zero = memory_type_index;
+
+ if ((physical_memory_properties_.memoryTypes[memory_type_index]
+ .propertyFlags &
+ flags) == flags) {
+ return memory_type_index;
+ }
+ }
+
+ ++memory_type_index;
+ memory_type_bits >>= 1;
+ }
+
+ if (force_flags)
+ return std::numeric_limits<uint32_t>::max();
+
+ return first_non_zero;
+}
+
+const VkMemoryRequirements Resource::GetVkBufferMemoryRequirements(
+ VkBuffer buffer) const {
+ VkMemoryRequirements requirement;
+ vkGetBufferMemoryRequirements(device_, buffer, &requirement);
+ return requirement;
+}
+
+const VkMemoryRequirements Resource::GetVkImageMemoryRequirements(
+ VkImage image) const {
+ VkMemoryRequirements requirement;
+ vkGetImageMemoryRequirements(device_, image, &requirement);
+ return requirement;
+}
+
+Resource::AllocateResult Resource::AllocateAndBindMemoryToVkBuffer(
+ VkBuffer buffer,
+ VkDeviceMemory* memory,
+ VkMemoryPropertyFlags flags,
+ bool force_flags) {
+ if (buffer == VK_NULL_HANDLE)
+ return {Result("Vulkan::Given VkBuffer is VK_NULL_HANDLE"), 0};
+
+ if (memory == nullptr)
+ return {Result("Vulkan::Given VkDeviceMemory pointer is nullptr"), 0};
+
+ auto requirement = GetVkBufferMemoryRequirements(buffer);
+
+ uint32_t memory_type_index =
+ ChooseMemory(requirement.memoryTypeBits, flags, force_flags);
+ if (memory_type_index == std::numeric_limits<uint32_t>::max())
+ return {Result("Vulkan::Find Proper Memory Fail"), 0};
+
+ Result r = AllocateMemory(memory, requirement.size, memory_type_index);
+ if (!r.IsSuccess())
+ return {r, 0};
+
+ return {BindMemoryToVkBuffer(buffer, *memory), memory_type_index};
+}
+
+Result Resource::AllocateMemory(VkDeviceMemory* memory,
+ VkDeviceSize size,
+ uint32_t memory_type_index) {
+ VkMemoryAllocateInfo alloc_info = {};
+ alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ alloc_info.allocationSize = size;
+ alloc_info.memoryTypeIndex = memory_type_index;
+ if (vkAllocateMemory(device_, &alloc_info, nullptr, memory) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkAllocateMemory Fail");
+
+ return {};
+}
+
+Result Resource::BindMemoryToVkBuffer(VkBuffer buffer, VkDeviceMemory memory) {
+ if (vkBindBufferMemory(device_, buffer, memory, 0) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkBindBufferMemory Fail");
+
+ return {};
+}
+
+Resource::AllocateResult Resource::AllocateAndBindMemoryToVkImage(
+ VkImage image,
+ VkDeviceMemory* memory,
+ VkMemoryPropertyFlags flags,
+ bool force_flags) {
+ if (image == nullptr)
+ return {Result("Vulkan::Given VkImage pointer is nullptr"), 0};
+
+ if (memory == nullptr)
+ return {Result("Vulkan::Given VkDeviceMemory pointer is nullptr"), 0};
+
+ auto requirement = GetVkImageMemoryRequirements(image);
+
+ uint32_t memory_type_index =
+ ChooseMemory(requirement.memoryTypeBits, flags, force_flags);
+ if (memory_type_index == std::numeric_limits<uint32_t>::max())
+ return {Result("Vulkan::Find Proper Memory Fail"), 0};
+
+ Result r = AllocateMemory(memory, requirement.size, memory_type_index);
+ if (!r.IsSuccess())
+ return {r, 0};
+
+ return {BindMemoryToVkImage(image, *memory), memory_type_index};
+}
+
+Result Resource::BindMemoryToVkImage(VkImage image, VkDeviceMemory memory) {
+ if (vkBindImageMemory(device_, image, memory, 0) != VK_SUCCESS)
+ return Result("Vulkan::Calling vkBindImageMemory Fail");
+
+ return {};
+}
+
+Result Resource::MapMemory(VkDeviceMemory memory) {
+ if (vkMapMemory(device_, memory, 0, VK_WHOLE_SIZE, 0, &memory_ptr_) !=
+ VK_SUCCESS) {
+ return Result("Vulkan::Calling vkMapMemory Fail");
+ }
+
+ return {};
+}
+
+void Resource::UnMapMemory(VkDeviceMemory memory) {
+ vkUnmapMemory(device_, memory);
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/resource.h b/src/vulkan/resource.h
new file mode 100644
index 0000000..c2530ae
--- /dev/null
+++ b/src/vulkan/resource.h
@@ -0,0 +1,101 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_RESOURCE_H_
+#define SRC_VULKAN_RESOURCE_H_
+
+#include <memory>
+
+#include "amber/result.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class Resource {
+ public:
+ virtual ~Resource();
+
+ virtual VkDeviceMemory GetHostAccessMemory() const {
+ return host_accessible_memory_;
+ }
+
+ virtual void Shutdown();
+
+ void* HostAccessibleMemoryPtr() const { return memory_ptr_; }
+
+ protected:
+ Resource(VkDevice device,
+ size_t size,
+ const VkPhysicalDeviceMemoryProperties& properties);
+ Result Initialize();
+ Result CreateVkBuffer(VkBuffer* buffer, VkBufferUsageFlags usage);
+
+ VkDevice GetDevice() const { return device_; }
+ VkBuffer GetHostAccessibleBuffer() const { return host_accessible_buffer_; }
+
+ size_t GetSize() const { return size_; }
+
+ struct AllocateResult {
+ Result r;
+ uint32_t memory_type_index;
+ };
+
+ AllocateResult AllocateAndBindMemoryToVkBuffer(VkBuffer buffer,
+ VkDeviceMemory* memory,
+ VkMemoryPropertyFlags flags,
+ bool force_flags);
+ AllocateResult AllocateAndBindMemoryToVkImage(VkImage image,
+ VkDeviceMemory* memory,
+ VkMemoryPropertyFlags flags,
+ bool force_flags);
+
+ bool CheckMemoryHostAccessible(uint32_t memory_type_index) {
+ return (physical_memory_properties_.memoryTypes[memory_type_index]
+ .propertyFlags &
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) ==
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT;
+ }
+
+ Result MapMemory(VkDeviceMemory memory);
+ void UnMapMemory(VkDeviceMemory memory);
+
+ private:
+ uint32_t ChooseMemory(uint32_t memory_type_bits,
+ VkMemoryPropertyFlags flags,
+ bool force_flags);
+ Result AllocateMemory(VkDeviceMemory* memory,
+ VkDeviceSize size,
+ uint32_t memory_type_index);
+
+ Result BindMemoryToVkBuffer(VkBuffer buffer, VkDeviceMemory memory);
+ const VkMemoryRequirements GetVkBufferMemoryRequirements(
+ VkBuffer buffer) const;
+
+ Result BindMemoryToVkImage(VkImage image, VkDeviceMemory memory);
+ const VkMemoryRequirements GetVkImageMemoryRequirements(VkImage image) const;
+
+ VkDevice device_ = VK_NULL_HANDLE;
+ size_t size_ = 0;
+ VkPhysicalDeviceMemoryProperties physical_memory_properties_;
+
+ VkBuffer host_accessible_buffer_ = VK_NULL_HANDLE;
+ VkDeviceMemory host_accessible_memory_ = VK_NULL_HANDLE;
+ void* memory_ptr_ = nullptr;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_RESOURCE_H_
diff --git a/src/vulkan/vertex_buffer.cc b/src/vulkan/vertex_buffer.cc
new file mode 100644
index 0000000..943cbb1
--- /dev/null
+++ b/src/vulkan/vertex_buffer.cc
@@ -0,0 +1,118 @@
+// Copyright 2018 The Amber 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.
+
+#include "src/vulkan/vertex_buffer.h"
+
+#include <cassert>
+#include <cstring>
+
+#include "src/make_unique.h"
+#include "src/vulkan/bit_copy.h"
+#include "src/vulkan/format_data.h"
+
+namespace amber {
+namespace vulkan {
+
+VertexBuffer::VertexBuffer(VkDevice device) : device_(device) {}
+
+VertexBuffer::~VertexBuffer() = default;
+
+void VertexBuffer::SetData(uint8_t location,
+ const Format& format,
+ const std::vector<Value>& values) {
+ vertex_attr_desc_.emplace_back();
+ // TODO(jaebaek): Support multiple binding
+ vertex_attr_desc_.back().binding = 0;
+ vertex_attr_desc_.back().location = location;
+ vertex_attr_desc_.back().format = ToVkFormat(format.GetFormatType());
+ vertex_attr_desc_.back().offset = stride_in_bytes_;
+
+ stride_in_bytes_ += format.GetByteSize();
+
+ formats_.push_back(format);
+ data_.push_back(values);
+}
+
+void VertexBuffer::FillVertexBufferWithData(VkCommandBuffer command) {
+ // Send vertex data from host to device.
+ uint8_t* ptr = static_cast<uint8_t*>(buffer_->HostAccessibleMemoryPtr());
+ for (uint32_t i = 0; i < GetVertexCount(); ++i) {
+ for (uint32_t j = 0; j < formats_.size(); ++j) {
+ const auto pack_size = formats_[j].GetPackSize();
+ if (pack_size) {
+ BitCopy::CopyValueToBuffer(ptr, data_[j][i], 0, pack_size);
+ ptr += pack_size / 8;
+ continue;
+ }
+
+ const auto& components = formats_[j].GetComponents();
+ uint8_t bit_offset = 0;
+
+ for (uint32_t k = 0; k < components.size(); ++k) {
+ uint8_t bits = components[k].num_bits;
+ BitCopy::CopyValueToBuffer(ptr, data_[j][i * components.size() + k],
+ bit_offset, bits);
+
+ assert(k == components.size() - 1 ||
+ static_cast<uint32_t>(bit_offset) + static_cast<uint32_t>(bits) <
+ 256);
+ bit_offset += bits;
+ }
+
+ ptr += formats_[j].GetByteSize();
+ }
+ }
+
+ ptr = static_cast<uint8_t*>(buffer_->HostAccessibleMemoryPtr());
+ buffer_->CopyToDevice(command);
+}
+
+void VertexBuffer::BindToCommandBuffer(VkCommandBuffer command) {
+ const VkDeviceSize offset = 0;
+ const VkBuffer buffer = buffer_->GetVkBuffer();
+ // TODO(jaebaek): Support multiple binding
+ vkCmdBindVertexBuffers(command, 0, 1, &buffer, &offset);
+}
+
+Result VertexBuffer::SendVertexData(
+ VkCommandBuffer command,
+ const VkPhysicalDeviceMemoryProperties& properties) {
+ if (!is_vertex_data_pending_)
+ return Result("Vulkan::Vertices data was already sent");
+
+ const size_t n_vertices = GetVertexCount();
+ if (n_vertices == 0)
+ return Result("Vulkan::Data for VertexBuffer is empty");
+
+ size_t bytes = stride_in_bytes_ * n_vertices;
+
+ if (!buffer_) {
+ buffer_ = MakeUnique<Buffer>(device_, bytes, properties);
+ Result r = buffer_->Initialize(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT);
+ if (!r.IsSuccess())
+ return r;
+ }
+
+ if (formats_.empty() || formats_[0].GetComponents().empty())
+ return Result("Vulkan::Formats for VertexBuffer is empty");
+
+ FillVertexBufferWithData(command);
+
+ is_vertex_data_pending_ = false;
+ return {};
+}
+
+} // namespace vulkan
+} // namespace amber
diff --git a/src/vulkan/vertex_buffer.h b/src/vulkan/vertex_buffer.h
new file mode 100644
index 0000000..ef0c242
--- /dev/null
+++ b/src/vulkan/vertex_buffer.h
@@ -0,0 +1,84 @@
+// Copyright 2018 The Amber 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.
+
+#ifndef SRC_VULKAN_VERTEX_BUFFER_H_
+#define SRC_VULKAN_VERTEX_BUFFER_H_
+
+#include <memory>
+#include <vector>
+
+#include "amber/result.h"
+#include "src/format.h"
+#include "src/value.h"
+#include "src/vulkan/buffer.h"
+#include "vulkan/vulkan.h"
+
+namespace amber {
+namespace vulkan {
+
+class VertexBuffer {
+ public:
+ explicit VertexBuffer(VkDevice device);
+ ~VertexBuffer();
+
+ Result SendVertexData(VkCommandBuffer command,
+ const VkPhysicalDeviceMemoryProperties& properties);
+ bool VertexDataSent() { return !is_vertex_data_pending_; }
+
+ void SetData(uint8_t location,
+ const Format& format,
+ const std::vector<Value>& values);
+
+ const std::vector<VkVertexInputAttributeDescription>& GetVertexInputAttr()
+ const {
+ return vertex_attr_desc_;
+ }
+
+ VkVertexInputBindingDescription GetVertexInputBinding() const {
+ VkVertexInputBindingDescription vertex_binding_desc = {};
+ vertex_binding_desc.binding = 0;
+ vertex_binding_desc.stride = stride_in_bytes_;
+ vertex_binding_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
+ return vertex_binding_desc;
+ }
+
+ size_t GetVertexCount() const {
+ if (data_.empty())
+ return 0;
+
+ return data_[0].size() / formats_[0].GetComponents().size();
+ }
+
+ void BindToCommandBuffer(VkCommandBuffer command);
+
+ private:
+ void FillVertexBufferWithData(VkCommandBuffer command);
+
+ VkDevice device_ = VK_NULL_HANDLE;
+
+ bool is_vertex_data_pending_ = true;
+
+ std::unique_ptr<Buffer> buffer_;
+ uint32_t stride_in_bytes_ = 0;
+
+ std::vector<Format> formats_;
+ std::vector<std::vector<Value>> data_;
+
+ std::vector<VkVertexInputAttributeDescription> vertex_attr_desc_;
+};
+
+} // namespace vulkan
+} // namespace amber
+
+#endif // SRC_VULKAN_VERTEX_BUFFER_H_
diff --git a/tests/cases/clear.amber b/tests/cases/clear.amber
new file mode 100644
index 0000000..b178987
--- /dev/null
+++ b/tests/cases/clear.amber
@@ -0,0 +1,31 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (0, 0, 0, 0)
diff --git a/tests/cases/clear_and_probe_all_wrong_color.expect_fail.amber b/tests/cases/clear_and_probe_all_wrong_color.expect_fail.amber
new file mode 100644
index 0000000..fda1da2
--- /dev/null
+++ b/tests/cases/clear_and_probe_all_wrong_color.expect_fail.amber
@@ -0,0 +1,33 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear
+
+# Expected pixel color is (0, 0, 0, 0)
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (1.0, 0, 0, 0)
diff --git a/tests/cases/clear_and_probe_small_wrong_color.expect_fail.amber b/tests/cases/clear_and_probe_small_wrong_color.expect_fail.amber
new file mode 100644
index 0000000..54eee4f
--- /dev/null
+++ b/tests/cases/clear_and_probe_small_wrong_color.expect_fail.amber
@@ -0,0 +1,33 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear
+
+# Expected pixel color is (0, 0, 0, 0)
+relative probe rect rgba (0.9, 0.9, 0.1, 0.1) (1.0, 0, 0, 0)
diff --git a/tests/cases/clear_and_probe_too_large_rect.expect_fail.amber b/tests/cases/clear_and_probe_too_large_rect.expect_fail.amber
new file mode 100644
index 0000000..ed58f41
--- /dev/null
+++ b/tests/cases/clear_and_probe_too_large_rect.expect_fail.amber
@@ -0,0 +1,33 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear
+
+# Probe rect is out of framebuffer i.e., 0.9 + 1.0 = 1.9 > 1.0
+relative probe rect rgba (0.9, 0.9, 1.0, 1.0) (1.0, 0, 0, 0)
diff --git a/tests/cases/clear_color.amber b/tests/cases/clear_color.amber
new file mode 100644
index 0000000..74433de
--- /dev/null
+++ b/tests/cases/clear_color.amber
@@ -0,0 +1,38 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear color 1 0.4 0.5 0.2
+clear
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (1.0, 0.4, 0.5, 0.2)
+relative probe rect rgba (0.9, 0.9, 0.1, 0.1) (1.0, 0.4, 0.5, 0.2)
+
+clear color 0.4 0.2 1 0.5
+clear
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (0.4, 0.2, 1.0, 0.5)
+relative probe rect rgba (0.9, 0.9, 0.1, 0.1) (0.4, 0.2, 1.0, 0.5)
diff --git a/tests/cases/clear_color_without_clear_command.expect_fail.amber b/tests/cases/clear_color_without_clear_command.expect_fail.amber
new file mode 100644
index 0000000..e367c6d
--- /dev/null
+++ b/tests/cases/clear_color_without_clear_command.expect_fail.amber
@@ -0,0 +1,34 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear color 0.1 0.2 0.3 0.4
+
+# No clear command before probe. Random pixels are expected.
+
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (0.1, 0.2, 0.3, 0.4)
diff --git a/tests/cases/multiple_clear_color.amber b/tests/cases/multiple_clear_color.amber
new file mode 100644
index 0000000..ede2d82
--- /dev/null
+++ b/tests/cases/multiple_clear_color.amber
@@ -0,0 +1,45 @@
+# Copyright 2018 The Amber Authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+[vertex shader passthrough]
+
+[fragment shader]
+#version 430
+
+layout(location = 0) in vec4 color_in;
+layout(location = 0) out vec4 color_out;
+
+void
+main()
+{
+ color_out = color_in;
+}
+
+[test]
+clear color 0 1.4 0.6 0.1
+clear color 1 0.4 0.5 0.2
+clear color 0.4 0.2 1 0.5
+clear
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (0.4, 0.2, 1.0, 0.5)
+relative probe rect rgba (0.9, 0.9, 0.1, 0.1) (0.4, 0.2, 1.0, 0.5)
+
+clear color 0 0 0 0
+clear color 0.4 0.2 1 0.5
+clear color 0 1.4 0.6 0.1
+clear color 1 0.4 0.5 0.2
+clear
+clear
+clear
+relative probe rect rgba (0.0, 0.0, 1.0, 1.0) (1.0, 0.4, 0.5, 0.2)
+relative probe rect rgba (0.9, 0.9, 0.1, 0.1) (1.0, 0.4, 0.5, 0.2)
diff --git a/tests/run_tests.py b/tests/run_tests.py
new file mode 100755
index 0000000..8353d25
--- /dev/null
+++ b/tests/run_tests.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python
+#
+# Copyright 2018 The Amber 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.
+
+import base64
+import difflib
+import optparse
+import os
+import re
+import subprocess
+import sys
+import tempfile
+
+class TestCase:
+ def __init__(self, input_path, parse_only):
+ self.input_path = input_path
+ self.parse_only = parse_only
+
+ self.results = {}
+
+ def IsExpectedFail(self):
+ fail_re = re.compile('^.+[.]expect_fail[.]amber')
+ return fail_re.match(self.GetInputPath())
+
+ def IsParseOnly(self):
+ return self.parse_only
+
+ def GetInputPath(self):
+ return self.input_path
+
+ def GetResult(self, fmt):
+ return self.results[fmt]
+
+
+class TestRunner:
+ def RunTest(self, tc):
+ print "Testing %s" % tc.GetInputPath()
+
+ cmd = [self.options.test_prog_path]
+ if tc.IsParseOnly():
+ cmd += ['-p']
+ cmd += [tc.GetInputPath()]
+
+ try:
+ subprocess.check_output(cmd)
+ except Exception as e:
+ if not tc.IsExpectedFail():
+ print e
+ return False
+
+ return True
+
+
+ def RunTests(self):
+ for tc in self.test_cases:
+ result = self.RunTest(tc)
+
+ if not tc.IsExpectedFail() and not result:
+ self.failures.append(tc.GetInputPath())
+ elif tc.IsExpectedFail() and result:
+ print("Expected: " + tc.GetInputPath() + " to fail but passed.")
+ self.failures.append(tc.GetInputPath())
+
+ def SummarizeResults(self):
+ if len(self.failures) > 0:
+ self.failures.sort()
+
+ print '\nSummary of Failures:'
+ for failure in self.failures:
+ print failure
+
+ print
+ print 'Test cases executed: %d' % len(self.test_cases)
+ print ' Successes: %d' % (len(self.test_cases) - len(self.failures))
+ print ' Failures: %d' % len(self.failures)
+ print
+
+
+ def Run(self):
+ base_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
+
+ usage = 'usage: %prog [options] (file)'
+ parser = optparse.OptionParser(usage=usage)
+ parser.add_option('--build-dir',
+ default=os.path.join(base_path, 'out', 'Debug'),
+ help='path to build directory')
+ parser.add_option('--test-dir',
+ default=os.path.join(os.path.dirname(__file__), 'cases'),
+ help='path to directory containing test files')
+ parser.add_option('--test-prog-path', default=None,
+ help='path to program to test')
+ parser.add_option('--parse-only',
+ action="store_true", default=False,
+ help='only parse test cases; do not execute')
+
+ self.options, self.args = parser.parse_args()
+
+ if self.options.test_prog_path == None:
+ test_prog = os.path.abspath(os.path.join(self.options.build_dir, 'amber'))
+ if not os.path.isfile(test_prog):
+ print "Cannot find test program %s" % test_prog
+ return 1
+
+ self.options.test_prog_path = test_prog
+
+ if not os.path.isfile(self.options.test_prog_path):
+ print "--test-prog-path must point to an executable"
+ return 1
+
+ input_file_re = re.compile('^.+[.]amber')
+ self.test_cases = []
+
+ if self.args:
+ for filename in self.args:
+ input_path = os.path.join(self.options.test_dir, filename)
+ if not os.path.isfile(input_path):
+ print "Cannot find test file '%s'" % filename
+ return 1
+
+ self.test_cases.append(TestCase(input_path, self.options.parse_only))
+
+ else:
+ for file_dir, _, filename_list in os.walk(self.options.test_dir):
+ for input_filename in filename_list:
+ if input_file_re.match(input_filename):
+ input_path = os.path.join(file_dir, input_filename)
+ if os.path.isfile(input_path):
+ self.test_cases.append(
+ TestCase(input_path, self.options.parse_only))
+
+ self.failures = []
+
+ self.RunTests()
+ self.SummarizeResults()
+
+ return len(self.failures) != 0
+
+def main():
+ runner = TestRunner()
+ return runner.Run()
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt
new file mode 100644
index 0000000..2e24bca
--- /dev/null
+++ b/third_party/CMakeLists.txt
@@ -0,0 +1,182 @@
+# Copyright 2018 The Amber 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.
+
+if(WIN32)
+ option(g`_force_shared_crt
+ "Use shared (DLL) run-time lib even when Google Test is built as static lib."
+ ON)
+endif()
+
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
+ # Turn off warnings to make gtest happy
+ set(GTEST_BUILD_FIXES
+ -Wno-covered-switch-default
+ -Wno-deprecated
+ -Wno-disabled-macro-expansion
+ -Wno-exit-time-constructors
+ -Wno-exit-time-destructors
+ -Wno-global-constructors
+ -Wno-missing-field-initializers
+ -Wno-format-nonliteral
+ -Wno-missing-noreturn
+ -Wno-missing-prototypes
+ -Wno-missing-variable-declarations
+ -Wno-shift-sign-overflow
+ -Wno-sign-conversion
+ -Wno-undef
+ -Wno-unused-member-function
+ -Wno-used-but-marked-unused
+ -Wno-weak-vtables
+ -Wno-zero-as-null-pointer-constant)
+
+ set(GLSLANG_BUILD_FIXES
+ -Wno-conversion
+ -Wno-covered-switch-default
+ -Wno-date-time
+ -Wno-deprecated
+ -Wno-disabled-macro-expansion
+ -Wno-double-promotion
+ -Wno-error
+ -Wno-exit-time-destructors
+ -Wno-extra-semi
+ -Wno-float-equal
+ -Wno-format-nonliteral
+ -Wno-format-pedantic
+ -Wno-global-constructors
+ -Wno-gnu-redeclared-enum
+ -Wno-implicit-fallthrough
+ -Wno-inconsistent-missing-destructor-override
+ -Wno-missing-field-initializers
+ -Wno-missing-noreturn
+ -Wno-missing-prototypes
+ -Wno-missing-variable-declarations
+ -Wno-newline-eof
+ -Wno-old-style-cast
+ -Wno-reserved-id-macro
+ -Wno-shadow
+ -Wno-shadow-field
+ -Wno-shadow-field-in-constructor
+ -Wno-shift-sign-overflow
+ -Wno-sign-conversion
+ -Wno-signed-enum-bitfield
+ -Wno-undef
+ -Wno-undefined-func-template
+ -Wno-undefined-reinterpret-cast
+ -Wno-unreachable-code
+ -Wno-unreachable-code-break
+ -Wno-unreachable-code-return
+ -Wno-unused-macros
+ -Wno-unused-parameter
+ -Wno-unused-variable
+ -Wno-used-but-marked-unused
+ -Wno-weak-vtables
+ -Wno-zero-as-null-pointer-constant)
+
+ set(SPIRV_TOOLS_BUILD_FIXES
+ -Wno-conditional-uninitialized
+ -Wno-covered-switch-default
+ -Wno-deprecated
+ -Wno-documentation
+ -Wno-documentation-pedantic
+ -Wno-double-promotion
+ -Wno-extra-semi
+ -Wno-float-equal
+ -Wno-format-nonliteral
+ -Wno-implicit-fallthrough
+ -Wno-missing-prototypes
+ -Wno-old-style-cast
+ -Wno-range-loop-analysis
+ -Wno-shift-sign-overflow
+ -Wno-unreachable-code-break
+ -Wno-unreachable-code-return
+ -Wno-unused-member-function
+ -Wno-weak-vtables
+ -Wno-zero-as-null-pointer-constant)
+
+ set(SHADERC_BUILD_FIXES
+ -Wno-comma
+ -Wno-conversion
+ -Wno-covered-switch
+ -Wno-covered-switch-default
+ -Wno-deprecated
+ -Wno-double-promotion
+ -Wno-extra-semi
+ -Wno-float-equal
+ -Wno-inconsistent-missing-destructor-override
+ -Wno-missing-field-initializers
+ -Wno-missing-prototypes
+ -Wno-newline-eof
+ -Wno-old-style-cast
+ -Wno-reserved-id-macro
+ -Wno-shadow-uncaptured-local
+ -Wno-shadow-field-in-constructor
+ -Wno-sign-conversion
+ -Wno-signed-enum-bitfield
+ -Wno-undef
+ -Wno-unreachable-code-return
+ -Wno-weak-vtables
+ -Wno-zero-as-null-pointer-constant
+ )
+endif()
+
+if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+ set(GTEST_BUILD_FIXES "")
+
+ set(GLSLANG_BUILD_FIXES
+ -Wno-error
+ -Wno-overflow
+ -Wno-missing-field-initializers
+ -Wno-pedantic
+ -Wno-unused-parameter
+ -Wno-unused-variable)
+
+ set(SPIRV_TOOLS_BUILD_FIXES "")
+
+ set(SHADERC_BUILD_FIXES
+ -Wno-missing-field-initializers
+ -Wno-pedantic)
+endif()
+
+set(CXX_BACK ${CMAKE_CXX_FLAGS})
+SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "${GTEST_BUILD_FIXES}")
+STRING(REGEX REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/googletest EXCLUDE_FROM_ALL)
+set(CMAKE_CXX_FLAGS ${CXX_BACK})
+
+set(CXX_BACK ${CMAKE_CXX_FLAGS})
+SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "${SPIRV_TOOLS_BUILD_FIXES}")
+STRING(REGEX REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+set(SPIRV-Headers_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/spirv-headers)
+set(SPIRV_SKIP_TESTS ON)
+set(SPIRV_SKIP_EXECUTABLES ON)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/spirv-tools)
+set(CMAKE_CXX_FLAGS ${CXX_BACK})
+
+set(CXX_BACK ${CMAKE_CXX_FLAGS})
+SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "${GLSLANG_BUILD_FIXES}")
+STRING(REGEX REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+set(ENABLE_HLSL ON)
+set(BUILD_TESTING OFF)
+set(ENABLE_GLSLANG_BINARIES OFF)
+set(ENABLE_SPVREMAPPER OFF)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/glslang)
+set(CMAKE_CXX_FLAGS ${CXX_BACK})
+
+set(CXX_BACK ${CMAKE_CXX_FLAGS})
+SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "${SHADERC_BUILD_FIXES}")
+STRING(REGEX REPLACE ";" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+set(SHADERC_THIRD_PARTY_ROOT_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE STRING "")
+set(SHADERC_SKIP_TESTS ON)
+add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/shaderc)
+set(CMAKE_CXX_FLAGS ${CXX_BACK})
diff --git a/tools/format b/tools/format
new file mode 100755
index 0000000..867f99d
--- /dev/null
+++ b/tools/format
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+find src -name "*.h" -exec clang-format -i {} \;
+find src -name "*.cc" -exec clang-format -i {} \;
+find samples -name "*.h" -exec clang-format -i {} \;
+find samples -name "*.cc" -exec clang-format -i {} \;
+find include -name "*.h" -exec clang-format -i {} \;
+
diff --git a/tools/git-sync-deps b/tools/git-sync-deps
new file mode 100755
index 0000000..74a0952
--- /dev/null
+++ b/tools/git-sync-deps
@@ -0,0 +1,275 @@
+#!/usr/bin/env python
+# Copyright 2014 Google Inc.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following disclaimer
+# in the documentation and/or other materials provided with the
+# distribution.
+# * Neither the name of Google Inc. nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Parse a DEPS file and git checkout all of the dependencies.
+
+Args:
+ An optional list of deps_os values.
+
+Environment Variables:
+ GIT_EXECUTABLE: path to "git" binary; if unset, will look for one of
+ ['git', 'git.exe', 'git.bat'] in your default path.
+
+ GIT_SYNC_DEPS_PATH: file to get the dependency list from; if unset,
+ will use the file ../DEPS relative to this script's directory.
+
+ GIT_SYNC_DEPS_QUIET: if set to non-empty string, suppress messages.
+
+Git Config:
+ To disable syncing of a single repository:
+ cd path/to/repository
+ git config sync-deps.disable true
+
+ To re-enable sync:
+ cd path/to/repository
+ git config --unset sync-deps.disable
+"""
+
+
+import os
+import subprocess
+import sys
+import threading
+
+
+def git_executable():
+ """Find the git executable.
+
+ Returns:
+ A string suitable for passing to subprocess functions, or None.
+ """
+ envgit = os.environ.get('GIT_EXECUTABLE')
+ searchlist = ['git', 'git.exe', 'git.bat']
+ if envgit:
+ searchlist.insert(0, envgit)
+ with open(os.devnull, 'w') as devnull:
+ for git in searchlist:
+ try:
+ subprocess.call([git, '--version'], stdout=devnull)
+ except (OSError,):
+ continue
+ return git
+ return None
+
+
+DEFAULT_DEPS_PATH = os.path.normpath(
+ os.path.join(os.path.dirname(__file__), os.pardir, 'DEPS'))
+
+
+def usage(deps_file_path = None):
+ sys.stderr.write(
+ 'Usage: run to grab dependencies, with optional platform support:\n')
+ sys.stderr.write(' %s %s' % (sys.executable, __file__))
+ if deps_file_path:
+ parsed_deps = parse_file_to_dict(deps_file_path)
+ if 'deps_os' in parsed_deps:
+ for deps_os in parsed_deps['deps_os']:
+ sys.stderr.write(' [%s]' % deps_os)
+ sys.stderr.write('\n\n')
+ sys.stderr.write(__doc__)
+
+
+def git_repository_sync_is_disabled(git, directory):
+ try:
+ disable = subprocess.check_output(
+ [git, 'config', 'sync-deps.disable'], cwd=directory)
+ return disable.lower().strip() in ['true', '1', 'yes', 'on']
+ except subprocess.CalledProcessError:
+ return False
+
+
+def is_git_toplevel(git, directory):
+ """Return true iff the directory is the top level of a Git repository.
+
+ Args:
+ git (string) the git executable
+
+ directory (string) the path into which the repository
+ is expected to be checked out.
+ """
+ try:
+ toplevel = subprocess.check_output(
+ [git, 'rev-parse', '--show-toplevel'], cwd=directory).strip()
+ return os.path.realpath(directory) == os.path.realpath(toplevel)
+ except subprocess.CalledProcessError:
+ return False
+
+
+def status(directory, checkoutable):
+ def truncate(s, length):
+ return s if len(s) <= length else s[:(length - 3)] + '...'
+ dlen = 36
+ directory = truncate(directory, dlen)
+ checkoutable = truncate(checkoutable, 40)
+ sys.stdout.write('%-*s @ %s\n' % (dlen, directory, checkoutable))
+
+
+def git_checkout_to_directory(git, repo, checkoutable, directory, verbose):
+ """Checkout (and clone if needed) a Git repository.
+
+ Args:
+ git (string) the git executable
+
+ repo (string) the location of the repository, suitable
+ for passing to `git clone`.
+
+ checkoutable (string) a tag, branch, or commit, suitable for
+ passing to `git checkout`
+
+ directory (string) the path into which the repository
+ should be checked out.
+
+ verbose (boolean)
+
+ Raises an exception if any calls to git fail.
+ """
+ if not os.path.isdir(directory):
+ subprocess.check_call(
+ [git, 'clone', '--quiet', repo, directory])
+
+ if not is_git_toplevel(git, directory):
+ # if the directory exists, but isn't a git repo, you will modify
+ # the parent repostory, which isn't what you want.
+ sys.stdout.write('%s\n IS NOT TOP-LEVEL GIT DIRECTORY.\n' % directory)
+ return
+
+ # Check to see if this repo is disabled. Quick return.
+ if git_repository_sync_is_disabled(git, directory):
+ sys.stdout.write('%s\n SYNC IS DISABLED.\n' % directory)
+ return
+
+ with open(os.devnull, 'w') as devnull:
+ # If this fails, we will fetch before trying again. Don't spam user
+ # with error infomation.
+ if 0 == subprocess.call([git, 'checkout', '--quiet', checkoutable],
+ cwd=directory, stderr=devnull):
+ # if this succeeds, skip slow `git fetch`.
+ if verbose:
+ status(directory, checkoutable) # Success.
+ return
+
+ # If the repo has changed, always force use of the correct repo.
+ # If origin already points to repo, this is a quick no-op.
+ subprocess.check_call(
+ [git, 'remote', 'set-url', 'origin', repo], cwd=directory)
+
+ subprocess.check_call([git, 'fetch', '--quiet'], cwd=directory)
+
+ subprocess.check_call([git, 'checkout', '--quiet', checkoutable], cwd=directory)
+
+ if verbose:
+ status(directory, checkoutable) # Success.
+
+
+def parse_file_to_dict(path):
+ dictionary = {}
+ execfile(path, dictionary)
+ return dictionary
+
+
+def git_sync_deps(deps_file_path, command_line_os_requests, verbose):
+ """Grab dependencies, with optional platform support.
+
+ Args:
+ deps_file_path (string) Path to the DEPS file.
+
+ command_line_os_requests (list of strings) Can be empty list.
+ List of strings that should each be a key in the deps_os
+ dictionary in the DEPS file.
+
+ Raises git Exceptions.
+ """
+ git = git_executable()
+ assert git
+
+ deps_file_directory = os.path.dirname(deps_file_path)
+ deps_file = parse_file_to_dict(deps_file_path)
+ dependencies = deps_file['deps'].copy()
+ os_specific_dependencies = deps_file.get('deps_os', dict())
+ if 'all' in command_line_os_requests:
+ for value in os_specific_dependencies.itervalues():
+ dependencies.update(value)
+ else:
+ for os_name in command_line_os_requests:
+ # Add OS-specific dependencies
+ if os_name in os_specific_dependencies:
+ dependencies.update(os_specific_dependencies[os_name])
+ for directory in dependencies:
+ for other_dir in dependencies:
+ if directory.startswith(other_dir + '/'):
+ raise Exception('%r is parent of %r' % (other_dir, directory))
+ list_of_arg_lists = []
+ for directory in sorted(dependencies):
+ if '@' in dependencies[directory]:
+ repo, checkoutable = dependencies[directory].split('@', 1)
+ else:
+ raise Exception("please specify commit or tag")
+
+ relative_directory = os.path.join(deps_file_directory, directory)
+
+ list_of_arg_lists.append(
+ (git, repo, checkoutable, relative_directory, verbose))
+
+ multithread(git_checkout_to_directory, list_of_arg_lists)
+
+ for directory in deps_file.get('recursedeps', []):
+ recursive_path = os.path.join(deps_file_directory, directory, 'DEPS')
+ git_sync_deps(recursive_path, command_line_os_requests, verbose)
+
+
+def multithread(function, list_of_arg_lists):
+ # for args in list_of_arg_lists:
+ # function(*args)
+ # return
+ threads = []
+ for args in list_of_arg_lists:
+ thread = threading.Thread(None, function, None, args)
+ thread.start()
+ threads.append(thread)
+ for thread in threads:
+ thread.join()
+
+
+def main(argv):
+ deps_file_path = os.environ.get('GIT_SYNC_DEPS_PATH', DEFAULT_DEPS_PATH)
+ verbose = not bool(os.environ.get('GIT_SYNC_DEPS_QUIET', False))
+
+ if '--help' in argv or '-h' in argv:
+ usage(deps_file_path)
+ return 1
+
+ git_sync_deps(deps_file_path, argv, verbose)
+ # subprocess.check_call(
+ # [sys.executable,
+ # os.path.join(os.path.dirname(deps_file_path), 'bin', 'fetch-gn')])
+ return 0
+
+
+if __name__ == '__main__':
+ exit(main(sys.argv[1:]))
diff --git a/tools/update_build_version.py b/tools/update_build_version.py
new file mode 100755
index 0000000..b353607
--- /dev/null
+++ b/tools/update_build_version.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+# Copyright 2018 The Amber Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Generates build-versions.h in the src/ directory.
+#
+# Args: <output_dir> <amber-dir> <spirv-tools-dir> <spirv-headers> <glslang-dir> <shaderc-dir>
+
+from __future__ import print_function
+
+import datetime
+import os.path
+import re
+import subprocess
+import sys
+import time
+
+OUTFILE = 'src/build-versions.h'
+
+
+def command_output(cmd, directory):
+ p = subprocess.Popen(cmd,
+ cwd=directory,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
+ (stdout, _) = p.communicate()
+ if p.returncode != 0:
+ raise RuntimeError('Failed to run {} in {}'.format(cmd, directory))
+ return stdout
+
+
+def describe(directory):
+ return command_output(
+ ['git', 'log', '-1', '--format=%h'], directory).rstrip().decode()
+
+
+def get_version_string(project, directory):
+ return "#define {}_VERSION \"{}\"".format(project.upper(), describe(directory))
+
+
+def main():
+ if len(sys.argv) != 7:
+ print('usage: {} <outdir> <amber-dir> <spirv-tools-dir> <spirv-headers> <glslang-dir> <shaderc-dir>'.format(
+ sys.argv[0]))
+ sys.exit(1)
+
+ outdir = sys.argv[1]
+
+ projects = ['amber', 'spirv_tools', 'spirv_headers', 'glslang', 'shaderc']
+ new_content = ''.join([
+ '{}\n'.format(get_version_string(p, d))
+ for (p, d) in zip(projects, sys.argv[2:])
+ ])
+
+ file = outdir + "/" + OUTFILE
+ if os.path.isfile(file):
+ with open(file, 'r') as f:
+ if new_content == f.read():
+ return
+ with open(file, 'w') as f:
+ f.write(new_content)
+
+
+if __name__ == '__main__':
+ main()