diff options
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 @@ -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/). @@ -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'], +} @@ -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, ¤t_type, ¤t_shader, ¤t_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, §ion_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", §ion_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, §ion_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, ®ion); +} + +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, ©_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() |