From e549e7b21b2ad60f01b8ecae10f01044be3dc4ab Mon Sep 17 00:00:00 2001 From: Andrew Woloszyn Date: Thu, 16 Jul 2015 11:07:40 -0400 Subject: [PATCH] Initial commit of shaderc. --- .gitignore | 8 + AUTHORS | 9 + CMakeLists.txt | 16 + CONTRIBUTING.md | 61 +++ CONTRIBUTORS | 16 + LICENSE | 201 +++++++++ README.md | 94 ++++ cmake/setup_build.cmake | 97 +++++ cmake/utils.cmake | 92 ++++ glslc/.clang-format | 5 + glslc/CMakeLists.txt | 38 ++ glslc/README.asciidoc | 261 +++++++++++ glslc/src/compilation_context.cc | 406 ++++++++++++++++++ glslc/src/compilation_context.h | 189 ++++++++ glslc/src/file.cc | 26 ++ glslc/src/file.h | 42 ++ glslc/src/file_includer.cc | 32 ++ glslc/src/file_includer.h | 45 ++ glslc/src/file_test.cc | 87 ++++ glslc/src/main.cc | 230 ++++++++++ glslc/src/message.cc | 244 +++++++++++ glslc/src/message.h | 79 ++++ glslc/src/message_test.cc | 184 ++++++++ glslc/src/shader_stage.cc | 74 ++++ glslc/src/shader_stage.h | 45 ++ glslc/src/version_profile.cc | 58 +++ glslc/src/version_profile.h | 40 ++ glslc/test/CMakeLists.txt | 6 + glslc/test/environment.py | 72 ++++ glslc/test/expect.py | 364 ++++++++++++++++ glslc/test/expect_nosetest.py | 33 ++ glslc/test/file_extensions.py | 86 ++++ glslc/test/glslc_test_framework.py | 333 ++++++++++++++ glslc/test/glslc_test_framework_nosetest.py | 104 +++++ glslc/test/include.py | 194 +++++++++ glslc/test/messages_tests.py | 322 ++++++++++++++ glslc/test/option_dash_D.py | 363 ++++++++++++++++ glslc/test/option_dash_E.py | 331 ++++++++++++++ glslc/test/option_dash_S.py | 165 +++++++ glslc/test/option_dash_c.py | 55 +++ glslc/test/option_dash_g.py | 55 +++ glslc/test/option_dash_o.py | 65 +++ glslc/test/option_dash_x.py | 96 +++++ glslc/test/option_shader_stage.py | 199 +++++++++ glslc/test/option_std.py | 252 +++++++++++ glslc/test/parameter_tests.py | 162 +++++++ glslc/test/placeholder.py | 117 +++++ glslc/test/pragma_shader_stage.py | 404 +++++++++++++++++ glslc/test/stdin_out.py | 37 ++ libshaderc/.clang-format | 5 + libshaderc/CMakeLists.txt | 23 + libshaderc/include/shaderc.h | 111 +++++ libshaderc/include/shaderc.hpp | 118 +++++ libshaderc/src/shaderc.cc | 181 ++++++++ libshaderc/src/shaderc_cpp_test.cc | 172 ++++++++ libshaderc/src/shaderc_private.h | 39 ++ libshaderc/src/shaderc_test.cc | 189 ++++++++ libshaderc_util/.clang-format | 5 + libshaderc_util/CMakeLists.txt | 37 ++ .../include/libshaderc_util/file_finder.h | 53 +++ .../include/libshaderc_util/format.h | 36 ++ libshaderc_util/include/libshaderc_util/io.h | 38 ++ .../include/libshaderc_util/resources.h | 27 ++ .../include/libshaderc_util/string_piece.h | 344 +++++++++++++++ .../libshaderc_util/universal_unistd.h | 32 ++ libshaderc_util/src/file_finder.cc | 48 +++ libshaderc_util/src/file_finder_test.cc | 124 ++++++ libshaderc_util/src/format_test.cc | 102 +++++ libshaderc_util/src/io.cc | 100 +++++ libshaderc_util/src/io_test.cc | 60 +++ libshaderc_util/src/resources.cc | 129 ++++++ libshaderc_util/src/string_piece_test.cc | 384 +++++++++++++++++ libshaderc_util/testdata/copy-to-build.cmake | 2 + .../testdata/dir/subdir/include_file.2 | 0 libshaderc_util/testdata/include_file.1 | 1 + third_party/CMakeLists.txt | 49 +++ utils/copy-tests-if-necessary.py | 117 +++++ utils/remove-file-by-suffix.py | 35 ++ 78 files changed, 9055 insertions(+) create mode 100644 .gitignore create mode 100644 AUTHORS create mode 100644 CMakeLists.txt create mode 100644 CONTRIBUTING.md create mode 100644 CONTRIBUTORS create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmake/setup_build.cmake create mode 100644 cmake/utils.cmake create mode 100644 glslc/.clang-format create mode 100644 glslc/CMakeLists.txt create mode 100644 glslc/README.asciidoc create mode 100644 glslc/src/compilation_context.cc create mode 100644 glslc/src/compilation_context.h create mode 100644 glslc/src/file.cc create mode 100644 glslc/src/file.h create mode 100644 glslc/src/file_includer.cc create mode 100644 glslc/src/file_includer.h create mode 100644 glslc/src/file_test.cc create mode 100644 glslc/src/main.cc create mode 100644 glslc/src/message.cc create mode 100644 glslc/src/message.h create mode 100644 glslc/src/message_test.cc create mode 100644 glslc/src/shader_stage.cc create mode 100644 glslc/src/shader_stage.h create mode 100644 glslc/src/version_profile.cc create mode 100644 glslc/src/version_profile.h create mode 100644 glslc/test/CMakeLists.txt create mode 100644 glslc/test/environment.py create mode 100644 glslc/test/expect.py create mode 100644 glslc/test/expect_nosetest.py create mode 100644 glslc/test/file_extensions.py create mode 100755 glslc/test/glslc_test_framework.py create mode 100644 glslc/test/glslc_test_framework_nosetest.py create mode 100644 glslc/test/include.py create mode 100644 glslc/test/messages_tests.py create mode 100644 glslc/test/option_dash_D.py create mode 100644 glslc/test/option_dash_E.py create mode 100644 glslc/test/option_dash_S.py create mode 100644 glslc/test/option_dash_c.py create mode 100644 glslc/test/option_dash_g.py create mode 100644 glslc/test/option_dash_o.py create mode 100644 glslc/test/option_dash_x.py create mode 100644 glslc/test/option_shader_stage.py create mode 100644 glslc/test/option_std.py create mode 100644 glslc/test/parameter_tests.py create mode 100644 glslc/test/placeholder.py create mode 100644 glslc/test/pragma_shader_stage.py create mode 100644 glslc/test/stdin_out.py create mode 100644 libshaderc/.clang-format create mode 100644 libshaderc/CMakeLists.txt create mode 100644 libshaderc/include/shaderc.h create mode 100644 libshaderc/include/shaderc.hpp create mode 100644 libshaderc/src/shaderc.cc create mode 100644 libshaderc/src/shaderc_cpp_test.cc create mode 100644 libshaderc/src/shaderc_private.h create mode 100644 libshaderc/src/shaderc_test.cc create mode 100644 libshaderc_util/.clang-format create mode 100644 libshaderc_util/CMakeLists.txt create mode 100644 libshaderc_util/include/libshaderc_util/file_finder.h create mode 100644 libshaderc_util/include/libshaderc_util/format.h create mode 100644 libshaderc_util/include/libshaderc_util/io.h create mode 100644 libshaderc_util/include/libshaderc_util/resources.h create mode 100644 libshaderc_util/include/libshaderc_util/string_piece.h create mode 100644 libshaderc_util/include/libshaderc_util/universal_unistd.h create mode 100644 libshaderc_util/src/file_finder.cc create mode 100644 libshaderc_util/src/file_finder_test.cc create mode 100644 libshaderc_util/src/format_test.cc create mode 100644 libshaderc_util/src/io.cc create mode 100644 libshaderc_util/src/io_test.cc create mode 100644 libshaderc_util/src/resources.cc create mode 100644 libshaderc_util/src/string_piece_test.cc create mode 100644 libshaderc_util/testdata/copy-to-build.cmake create mode 100644 libshaderc_util/testdata/dir/subdir/include_file.2 create mode 100644 libshaderc_util/testdata/include_file.1 create mode 100644 third_party/CMakeLists.txt create mode 100755 utils/copy-tests-if-necessary.py create mode 100755 utils/remove-file-by-suffix.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..8c38abbf1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +build/ +build-*/ +*.pyc +compile_commands.json +.ycm_extra_conf.py +cscope.* +third_party/glslang/ +ext/gmock-1.7.0/ diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..9bd9eaa7b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,9 @@ +# This is the official list of shaderc authors for copyright purposes. +# This file is distinct from the CONTRIBUTORS files. +# See the latter for an explanation. + +# Names should be added to this file as: +# Name or Organization +# The email address is not required for organizations. + +Google Inc. diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..015cccae2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 2.8.12) +project(shaderc) +enable_testing() + +include(cmake/setup_build.cmake) +include(cmake/utils.cmake) + +# Configure subdirectories. +# We depend on these for later projects, so they should come first. +add_subdirectory(third_party) + +add_subdirectory(libshaderc_util) +add_subdirectory(libshaderc) +add_subdirectory(glslc) + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..4294159be --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,61 @@ +Want to contribute? Great! First, read this page (including the small print at the end). + +## Before you contribute + +Before we can use your code, you must sign the +[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) +(CLA), which you can do online. The CLA is necessary mainly because you own the +copyright to your changes, even after your contribution becomes part of our +codebase, so we need your permission to use and distribute your code. We also +need to be sure of various other things -- for instance that you'll tell us if +you know that your code infringes on other people's patents. You don't have to +sign the CLA until after you've submitted your code for review and a member has +approved it, but you must do it before we can put your code into our codebase. + +Before you start working on a larger contribution, you should get in touch with +us first through the issue tracker with your idea so that we can help out and +possibly guide you. Coordinating up front makes it much easier to avoid +frustration later on. + +## Coding style + +For our C++ files, we use the +[Google C++ style guide](http://google-styleguide.googlecode.com/svn/trunk/cppguide.html). +(Conveniently, the formatting rules it specifies can be achieved using +`clang-format -style=google`.) + +For our Python files, we use the +[Google Python style guide](https://google-styleguide.googlecode.com/svn/trunk/pyguide.html). + +## Bug tracking + +We use GitHub's issue-tracker mechanism for submitting and resolving bugs in +Shaderc. + +## Code reviews + +All submissions, including submissions by project members, require review. We +use GitHub pull requests for this purpose. + +A pull request may be accepted by any one of the package maintainers. You +should expect the maintainers to strictly insist on the +[commenting](http://google-styleguide.googlecode.com/svn/trunk/cppguide.html#Comments) +guidelines -- in particular, every file, class, method, data member, and global +will require a comment. The maintainers will also expect to see test coverage +for every code change. _How much_ coverage will be a judgment call on a +case-by-case basis, balancing the required effort against the incremental +benefit. But coverage will be expected. As a matter of development philosophy, +we will strive to engineer the code to make writing tests easy. + +## Supported platforms + +We expect Shaderc to always build and test successfully on the platforms listed +below. Please keep that in mind when offering contributions. This list will +likely grow over time. + +* Linux x86 + +## The small print + +Contributions made by corporations are covered by a different agreement than +the one above, the Software Grant and Corporate Contributor License Agreement. diff --git a/CONTRIBUTORS b/CONTRIBUTORS new file mode 100644 index 000000000..6f146c21b --- /dev/null +++ b/CONTRIBUTORS @@ -0,0 +1,16 @@ +# People who have agreed to one of the CLAs and can contribute patches. +# The AUTHORS file lists the copyright holders; this file +# lists people. For example, Google employees are listed here +# but not in AUTHORS, because Google holds the copyright. +# +# https://developers.google.com/open-source/cla/individual +# https://developers.google.com/open-source/cla/corporate +# +# Names should be added to this file as: +# Name + +Lei Zhang +David Neto +Andrew Woloszyn +Stefanus Du Toit +Dejan Mircevski diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + 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 000000000..5691051fb --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# shaderc + +A collection of tools, libraries and tests for shader compilation. + +## Status + +Shaderc is experimental, and subject to significant incompatible changes. + +For licensing terms, please see the `LICENSE` file. If interested in +contributing to this project, please see `CONTRIBUTING.md`. + +This is not an official Google product (experimental or otherwise), it is just +code that happens to be owned by Google. That may change if Shaderc gains +contributions from others. See the `CONTRIBUTING.md` file for more information. +See also the `AUTHORS` and `CONTRIBUTORS` files. + +## File organization + +- `glslc/`: an executable to compile GLSL to SPIR-V +- `libshaderc/`: a library for compiling shader strings into SPIR-V +- `libshaderc_util/`: a utility library used by multiple shaderc components +- `third_party/`: third party open source packages; see below + +shaderc depends on a fork of the Khronos reference GLSL compiler glslang. +shaderc also depends on the testing framework googlemock. + +In the following example, $SOURCE_DIR is the directory you intend to +clone shaderc into. + +## Get started + +1) Check out the source code: + + git clone https://github.com/google/shaderc $SOURCE_DIR + cd $SOURCE_DIR/third_party + svn checkout http://googlemock.googlecode.com/svn/tags/release-1.7.0 \ + gmock-1.7.0 + git clone https://github.com/google/glslang.git glslang + cd $SOURCE_DIR/ + +2) Decide where to place the build outputs. In the following steps, we'll + call it $BUILD_DIR. Any new directory should work. We recommend building + outside the source tree, but it is common to build in a subdirectory of + $SOURCE_DIR, such as $SOURCE_DIR/build. + +3a) Build without code coverage (Linux, Windows with Ninja): + + cd $BUILD_DIR + cmake -GNinja -DCMAKE_BUILD_TYPE={Debug|Release|RelWithDebInfo} $SOURCE_DIR + ninja + ctest + +3b) Build without coverage (Windows with MSVC): + + cd $BUILD_DIR + cmake $SOURCE_DIR + cmake --build . --config {Release|Debug|MinSizeRel|RelWithDebInfo} + ctest -C {Release|Debug|MinSizeRel|RelWithDebInfo} + +3c) Build with code coverage (Linux): + + cd $BUILD_DIR + cmake -GNinja -DCMAKE_BUILD_TYPE=Debug -DENABLE_CODE_COVERAGE=ON + $SOURCE_DIR + ninja + ninja report-coverage + +Then the coverage report can be found under the `$BUILD_DIR/coverage-report +directory. + +### Tool dependencies + +For building, testing, and profiling shaderc, the following common tools +should be installed: + +- [cmake](http://www.cmake.org/): For generating compilation targets. +- [python](http://www.python.org/): For running the test suite. + +On Linux, the following tools should be installed: +- [gcov](https://gcc.gnu.org/onlinedocs/gcc/Gcov.html): for testing code + coverage, provided by the `gcc` package on Ubuntu. +- [lcov](http://ltp.sourceforge.net/coverage/lcov.php): a graphical frontend for + gcov, provided by the `lcov` package on Ubuntu. +- [genhtml](http://linux.die.net/man/1/genhtml): for creating reports in html + format from lcov output, provided by the `lcov` package on Ubuntu. + +On Windows, the following tools should be installed and available on your path. + - Visual Studio 2013 Update 4 or later. Previous versions of Visual Studio + will likely work but are untested. + - git - including the associated tools, bash, diff. + +Optional: for all platforms + - [asciidoctor](http://asciidoctor.org/): for generating documenation. + - [nosetests](https://nose.readthedocs.org): for testing the Python code. diff --git a/cmake/setup_build.cmake b/cmake/setup_build.cmake new file mode 100644 index 000000000..99bbbbf90 --- /dev/null +++ b/cmake/setup_build.cmake @@ -0,0 +1,97 @@ +# Find nosetests; see add_nosetests() from utils.cmake for opting in to +# nosetests in a specific directory. +find_program(NOSETESTS_EXE NAMES nosetests) +if (NOT NOSETESTS_EXE) + message(STATUS "nosetests was not found - python code will not be tested") +endif() + +# Find asciidoctor; see add_asciidoc() from utils.cmake for +# adding documents. +find_program(ASCIIDOCTOR_EXE NAMES asciidoctor) +if (NOT ASCIIDOCTOR_EXE) + message(STATUS "asciidoctor was not found - no documentation will be" + " generated") +endif() + +# On Windows, CMake by default compiles with the shared CRT. +# Ensure that gmock compiles the same, otherwise failures will occur. +if(WIN32) + # TODO(awoloszyn): Once we support selecting CRT versions, + # make sure this matches correctly. + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) +endif(WIN32) + +find_program(PYTHON_EXE python REQUIRED) + +option(ENABLE_CODE_COVERAGE "Enable collecting code coverage." OFF) +if (ENABLE_CODE_COVERAGE) + if (NOT UNIX) + message(FATAL_ERROR "Code coverage on non-UNIX system not supported yet.") + endif() + if (NOT "${CMAKE_BUILD_TYPE}" STREQUAL "Debug") + message(FATAL_ERROR "Code coverage with non-Debug build can be misleading.") + endif() + find_program(LCOV_EXE NAMES lcov) + if (NOT LCOV_EXE) + message(FATAL_ERROR "lcov was not found") + endif() + find_program(GENHTML_EXE NAMES genhtml) + if (NOT GENHTML_EXE) + message(FATAL_ERROR "genhtml was not found") + endif() + + set(LCOV_BASE_DIR ${CMAKE_BINARY_DIR}) + set(LCOV_INFO_FILE ${LCOV_BASE_DIR}/lcov.info) + set(COVERAGE_STAT_HTML_DIR ${LCOV_BASE_DIR}/coverage-report) + + add_custom_target(clean-coverage + # Remove all gcov .gcda files in the directory recursively. + COMMAND ${LCOV_EXE} --directory . --zerocounters -q + # Remove all lcov .info files. + COMMAND ${CMAKE_COMMAND} -E remove ${LCOV_INFO_FILE} + # Remove all html report files. + COMMAND ${CMAKE_COMMAND} -E remove_directory ${COVERAGE_STAT_HTML_DIR} + # TODO(antiagainst): the following two commands are here to remedy the + # problem of "reached unexpected end of file" experienced by lcov. + # The symptom is that some .gcno files are wrong after code change and + # recompiling. We don't know the exact reason yet. Figure it out. + # Remove all .gcno files in the directory recursively. + COMMAND ${PYTHON_EXE} + ${shaderc_SOURCE_DIR}/utils/remove-file-by-suffix.py . ".gcno" + # .gcno files are not tracked by CMake. So no recompiling is triggered + # even if they are missing. Unfortunately, we just removed all of them + # in the above. + COMMAND ${CMAKE_COMMAND} --build . --target clean + WORKING_DIRECTORY ${LCOV_BASE_DIR} + COMMENT "Clean coverage files" + ) + + add_custom_target(report-coverage + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + # Run all tests. + COMMAND ctest --output-on-failure + # Collect coverage data from gcov .gcda files. + COMMAND ${LCOV_EXE} --directory . --capture -o ${LCOV_INFO_FILE} + # Remove coverage info for system header files. + COMMAND ${LCOV_EXE} + --remove ${LCOV_INFO_FILE} '/usr/include/*' -o ${LCOV_INFO_FILE} + # Remove coverage info for external and third_party code. + COMMAND ${LCOV_EXE} + --remove ${LCOV_INFO_FILE} '${shaderc_SOURCE_DIR}/ext/*' + -o ${LCOV_INFO_FILE} + + COMMAND ${LCOV_EXE} + --remove ${LCOV_INFO_FILE} '${shaderc_SOURCE_DIR}/third_party/*' + -o ${LCOV_INFO_FILE} + # Remove coverage info for tests. + COMMAND ${LCOV_EXE} + --remove ${LCOV_INFO_FILE} '*_test.cc' -o ${LCOV_INFO_FILE} + # Generate html report file. + COMMAND ${GENHTML_EXE} + ${LCOV_INFO_FILE} -t "Coverage Report" -o ${COVERAGE_STAT_HTML_DIR} + DEPENDS clean-coverage + WORKING_DIRECTORY ${LCOV_BASE_DIR} + COMMENT "Collect and analyze coverage data" + ) +endif() + diff --git a/cmake/utils.cmake b/cmake/utils.cmake new file mode 100644 index 000000000..7f43e6d8d --- /dev/null +++ b/cmake/utils.cmake @@ -0,0 +1,92 @@ +# utility functions + +function (use_gmock TARGET) + target_include_directories(${TARGET} PRIVATE + ${gmock_SOURCE_DIR}/include + ${gtest_SOURCE_DIR}/include) + target_link_libraries(${TARGET} PRIVATE gmock gtest_main) +endfunction(use_gmock) + +function(default_compile_options TARGET) + if (NOT "${MSVC}") + target_compile_options(${TARGET} PRIVATE -std=c++11 -fPIC -Wall -Werror) + if (ENABLE_CODE_COVERAGE) + # The --coverage option is a synonym for -fprofile-arcs -ftest-coverage + # when compiling. + target_compile_options(${TARGET} PRIVATE -g -O0 --coverage) + # The --coverage option is a synonym for -lgcov when linking for gcc. + # For clang, it links in a different library, libclang_rt.profile, which + # requires clang to be built with compiler-rt. + target_link_libraries(${TARGET} PRIVATE --coverage) + endif() + else() + # disable warning C4800: 'int' : forcing value to bool 'true' or 'false' + # (performance warning) + target_compile_options(${TARGET} PRIVATE /wd4800) + endif() +endfunction(default_compile_options) + +# Build an asciidoc file; additional arguments past the base filename specify +# additional dependencies for the file. +function(add_asciidoc TARGET FILE) + if (ASCIIDOCTOR_EXE) + set(DEST ${CMAKE_CURRENT_BINARY_DIR}/${FILE}.html) + add_custom_command( + COMMAND ${ASCIIDOCTOR_EXE} -a toc -o ${DEST} + ${CMAKE_CURRENT_SOURCE_DIR}/${FILE}.asciidoc + DEPENDS ${FILE}.asciidoc ${ARGN} + OUTPUT ${DEST}) + add_custom_target(${TARGET} ALL DEPENDS ${DEST}) + endif(ASCIIDOCTOR_EXE) +endfunction() + +# Run nosetests in the current directory. Nosetests will look for python files +# with "nosetest" in their name, as well as descending into directories with +# "nosetest" in their name. The test name will be ${PREFIX}_nosetests. +function(add_nosetests PREFIX) + if(NOSETESTS_EXE) + add_test( + NAME ${PREFIX}_nosetests + COMMAND ${NOSETESTS_EXE} + -m "(?:^|[b_./-])[Nn]ose[Tt]est" -v ${CMAKE_CURRENT_SOURCE_DIR}) + endif() +endfunction() + +# Adds a set of tests. +# This function accepts the following parameters: +# TEST_PREFIX: a prefix for each test target name +# TEST_NAMES: a list of test names where each TEST_NAME has a corresponding +# file residing at src/${TEST_NAME}_test.cc +# LINK_LIBS: (optional) a list of libraries to be linked to the test target +# INCLUDE_DIRS: (optional) a list of include directories to be searched +# for header files. +function(add_shaderc_tests) + cmake_parse_arguments(PARSED_ARGS + "" + "TEST_PREFIX" + "TEST_NAMES;LINK_LIBS;INCLUDE_DIRS" + ${ARGN}) + if (NOT PARSED_ARGS_TEST_NAMES) + message(FATAL_ERROR "Tests must have a target") + endif() + if (NOT PARSED_ARGS_TEST_PREFIX) + message(FATAL_ERROR "Tests must have a prefix") + endif() + foreach(TARGET ${PARSED_ARGS_TEST_NAMES}) + set(TEST_NAME ${PARSED_ARGS_TEST_PREFIX}_${TARGET}_test) + add_executable(${TEST_NAME} src/${TARGET}_test.cc) + default_compile_options(${TEST_NAME}) + if (PARSED_ARGS_LINK_LIBS) + target_link_libraries(${TEST_NAME} PRIVATE + ${PARSED_ARGS_LINK_LIBS}) + endif() + if (PARSED_ARGS_INCLUDE_DIRS) + target_include_directories(${TEST_NAME} PRIVATE + ${PARSED_ARGS_INCLUDE_DIRS}) + endif() + use_gmock(${TEST_NAME}) + add_test( + NAME ${PARSED_ARGS_TEST_PREFIX}_${TARGET} + COMMAND ${TEST_NAME}) + endforeach() +endfunction(add_shaderc_tests) diff --git a/glslc/.clang-format b/glslc/.clang-format new file mode 100644 index 000000000..e209e8cb8 --- /dev/null +++ b/glslc/.clang-format @@ -0,0 +1,5 @@ +--- +# Use Google code formatting rules. +Language: Cpp +BasedOnStyle: Google +... diff --git a/glslc/CMakeLists.txt b/glslc/CMakeLists.txt new file mode 100644 index 000000000..872f077f1 --- /dev/null +++ b/glslc/CMakeLists.txt @@ -0,0 +1,38 @@ +find_package(Threads) + +add_library(glslc STATIC + src/compilation_context.cc + src/compilation_context.h + src/file.cc + src/file.h + src/file_includer.cc + src/file_includer.h + src/message.cc + src/message.h + src/shader_stage.cc + src/shader_stage.h + src/version_profile.cc + src/version_profile.h +) + +default_compile_options(glslc) +target_include_directories(glslc PUBLIC ${glslang_SOURCE_DIR}) +target_link_libraries(glslc PRIVATE glslang OSDependent OGLCompiler + glslang SPIRV ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(glslc PRIVATE shaderc_util) + +add_executable(glslc_exe src/main.cc) +default_compile_options(glslc_exe) +set_target_properties(glslc_exe PROPERTIES OUTPUT_NAME glslc) +target_link_libraries(glslc_exe PRIVATE glslc shaderc_util) + +add_shaderc_tests( + TEST_PREFIX glslc + LINK_LIBS glslc shaderc_util + TEST_NAMES + file + message) + +add_asciidoc(glslc_doc_README README) + +add_subdirectory(test) diff --git a/glslc/README.asciidoc b/glslc/README.asciidoc new file mode 100644 index 000000000..624d15585 --- /dev/null +++ b/glslc/README.asciidoc @@ -0,0 +1,261 @@ += glslc Manual + +:numbered: +:source-highlighter: pygments + +== Name + +`glslc` - A command-line GLSL to SPIR-V compiler with Clang-compatible arguments. + +== Synopsis + +---- +glslc [-c|-S|-E] + [-Dmacroname[=value]...] + [-std=standard] [-x glsl] + [-fshader-stage=...] + [-g] + [-w] [-Werror] + [-o outfile] shader... +---- + +== Description + +=== Shader stage specification + +glslc provides three ways to specify the shader stage of a input shader file: +`-fshader-stage=`, `#pragma shader_stage()`, and file extension. +The `-fshader-stage=` option overrules `#pragma shader_stage()`, which then +overrules the file extension. + +Shader stages can be specified by naming a file with an appropriate extension +as shown in the following table. `-fshader-stage=` and `#pragma shader_stage()`, +on the other hand, enable you to specify shader stages from the command line +and within the source file. Possible ``stage``s for them are also listed in +the following table. Details about `-fshader-stage=` can be found in +<>. + +[[shader-stage-selection]] +.Shader Stage Selection +|=== +|Shader Stage |Shader File Extension |`` + +|vertex |`.vert` |`vertex` +|fragment |`.frag` |`fragment` +|tesselation control |`.tesc` |`tesscontrol` +|tesselation evaluation |`.tese` |`tesseval` +|geometry |`.geom` |`geometry` +|compute |`.comp` |`compute` +|=== + +`#pragma shader_stage()` relies on the `#pragma` preprocessor directive; thus, +the token inside `shader_stage()` is not subject to preprocessor macro +expansion. It must be exactly one of the ``stage``s in the above table. + +`#pragma shader_stage()` behaves as follows: + +* The first `#pragma shader_stage()` directive in a translation unit must + precede any non-preprocessor tokens. +* If there is more than one `#pragma shader_stage()` directive in the same + translation unit, all the ``stage``s specified must be the same. Otherwise, + glslc will issue an error. + +[[output-file-naming]] +=== Output file naming + +If a name is specified via `-o`, the output file will be given that name. +Otherwise, + +* If a compilation stage selection option is given (`-S` or `-c`), there will + be one output file generated per input shader file. The generated output file + will end with a file extension that matches the compilation stage, which is + `.s` for `-S` and `.spv` for `-c`. The name will depend on the original file's + name and extension. +** If the input file has a <>, the output file will be named as by appending the file extension + for the compilation stage to the input file's name. E.g., `glslc -c foo.vert` + will generate `foo.vert.spv`, and `glslc -s bar.frag` will generate + `bar.frag.s`. +** Otherwise, the output file will be named as by replacing the input file's + file extension, if any, with the file extension for the compilation stage. + E.g., `glslc -c foo` will generate `foo.spv`, and `glslc -s bar.glsl` will + generate `bar.s`. +* If no compilation stage is selected, the output file will be named `a.spv`. + +== Command Line Options + +=== Overall Options + +==== `--help` + +`--help` tells the glslc compiler to display all available options and exit. + +==== `-o` + +`-o` lets you specify the output file's name. It cannot be used when there are +multiple files generated. A filename of `-` represents standard output. + +=== Language and Mode Selection Options + +[[option-f-shader-stage]] +==== `-fshader-stage=` + +`-fshader-stage=` lets you specify the shader stage for one or more +inputs from the command line. + +Possible values for ```` are listed in the <> table. + +`-fshader-stage=` behaves as follows: + +* `-fshader-stage=` sets the shader stage for subsequent input files. It does + not affect the stages of any preceding inputs on the command line. +* When supplying more than one `-fshader-stage=` argument, the most recent + argument preceding an input file applies. +* A shader file not ending with <> must have a `-fshader-stage=` argument ahead of it to specify + its stage. +* If there is a `-fshader-stage=` before a file in which there is a `#pragma + shader_stage()` directive, the directive is ignored and the `-fshader-stage=` + argument is used instead. +* If there is a `-fshader-stage=` before a file with a known shader file + extension, the file extension is ignored and the `-fshader-stage=` argument + is used instead. + +==== `-std=` + +`-std=` lets you specify a shader version and profile on the command +line. ```` can be any valid concatenation of a GLSL version number and +profile, e.g., `310es`, `450core`, etc. The profile can be omitted as allowed by +GLSL, e.g., `450`. + +`-std=` behaves as follows: + +* `-std=` affects the version of all inputs passed to `glslc`. +* `-std=` overwrites `#version` directives in all input shaders, including those + preceding the argument. +* If a `-std=` argument specifies a different version from a `#version` + directive in an input file, `glslc` will issue a warning. +* If multiple `-std=` arguments are specified on the command line, only the last + one takes effect. + +==== `-x` + +`-x` lets you specify the language of the input shader files. The only accepted +argument is `glsl`. + +=== Compilation Stage Selection Options + +==== `-c` + +`-c` tells the glslc compiler to run the preprocessing and compiling stage. +Each input shader file results in a SPIR-V binary file; these SPIR-V binary +files are named by the rules in the <> +section. + +==== `-E` + +`-E` tells the glslc compiler to run only the preprocessing stage. It overrules +`-c` and `-S`. Preprocessed output is written to standard output, while +preprocessing errors are written to standard error. If multiple input shader +files are given, their preprocessed output are all written to standard output, +in the order specified on the command line. + +==== `-S` + +`-S` tells the glslc compiler to run the preprocessing, compiling, and then +disassembling stage. It overrules `-c`. Each input shader file results in a +SPIR-V assembly file; these SPIR-V assembly files are named by the rules in the +<> section. + +==== No Compilation Stage Selection + +If none of the above options is given, the glslc compiler will run +preprocessing, compiling, and linking stages. + +WARNING: Linking of multiple input shader files are not supported yet. + +=== Preprocessor Options + +==== `-D` + +`-Dmacroname[=[value]]` lets you define a preprocessor macro before input shader +files are preprocessed. If `value` is omitted, the macro is defined with an +empty value. + +=== Code Generation Options + +==== `-g` + +Requests that the compiler place source-level debug information into the object +code, such as identifier names and line numbers. + +NOTE: Currently this option has no effect. Full functionality depends on +glslang support for generating debug info. + +=== Warning and Error Options + +==== `-w` + +`-w` suppresses all warning output from `glslc`. Any warning that would have +been generated is silently ignored. + +==== `-Werror` + +`-Werror` forces any warning to be treated as an error in `glslc`. This means +that all `warning:` messages are shown as `error:` and any warnings will cause +a non-zero exit code from `glslc`. If `-w` is specified the warnings +generated are suppressed before they are converted to errors. + +== Divergence from and extensions to GLSL specifications + +=== Source-filename-based `#line` and `\\__FILE__` + +This section describes how the glslc compiler extends the syntax for `#line` and +`\\__FILE__`. When the `-fcpp-style-line-directive` option is supplied, this +extended syntax is generated during emitting a preprocessed output (the `-E` +option), and is parsed and accepted by the preprocessor. + +WARNING: This section is still evolving. Expect changes. + +GLSL specifications have a notion of source strings. + +[quote, Section 3.2 of both version 3.30 and 4.50] +____ +The source for a single shader is an array of strings of characters from the +character set. A single shader is made from the concatenation of these strings. +____ + +With the above notion, the second parameter to the `#line` directive should +be a constant integer expressions representing the source string number. Also +the `\\__FILE__` macro will "substitute a decimal integer constant that says +which source string number is currently being processed." + +The glslc compiler implements the standard `#line` and `\\__FILE__` syntax. It +also provides an extension to allow source filenames to be used instead of +integer source string indices. Specifically, the `#line` directive can have, +after macro substitution, one of the following three forms: + +[source,glsl] +---- +#line line-number +#line line-number integer-source-string-index +#line line-number "source-filename" +---- + +where `source-filename` can be any combinations of characters except double +quotation marks. (Note that according to the GLSL specification, "there are +no escape sequences or other uses of the backslash beyond use as the +line-continuation character".) + +And if source-filename-based `#line` is used, the `\\__FILE__` macro expands to +a string whose contents are the filename quoted with double quotation marks. +The filename is dertermined as the last of + +* The filename given to the glslc compiler, +* The filename argument of the most recent `#line` directive, if any. + +Source-filename-based `#line`/`\\__FILE__` is enabled by the +`-fcpp-style-line-directive` option. It is incompatible with +source-string-number-based `#line`/`\\__FILE__`; only one can be enabled. +glslc will issue an error if both are used. diff --git a/glslc/src/compilation_context.cc b/glslc/src/compilation_context.cc new file mode 100644 index 000000000..c02974cf7 --- /dev/null +++ b/glslc/src/compilation_context.cc @@ -0,0 +1,406 @@ +// Copyright 2015 The Shaderc 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. + +#include "compilation_context.h" + +#include + +#include "libshaderc_util/format.h" +#include "libshaderc_util/io.h" +#include "libshaderc_util/resources.h" +#include "libshaderc_util/string_piece.h" + +#include "SPIRV/disassemble.h" +#include "SPIRV/doc.h" +#include "SPIRV/GLSL450Lib.h" +#include "SPIRV/GlslangToSpv.h" + +#include "file.h" +#include "message.h" +#include "shader_stage.h" +#include "version_profile.h" + +// We must have the following global variable because declared as extern in +// glslang/SPIRV/disassemble.cpp, which we will need for disassembling. +const char* GlslStd450DebugNames[GLSL_STD_450::Count]; + +namespace { +using shaderc_util::string_piece; + +// For use with glslang parsing calls. +const bool kNotForwardCompatible = false; + +// Returns true if #line directive sets the line number for the next line in the +// given version and profile. +inline bool LineDirectiveIsForNextLine(int version, EProfile profile) { + return profile == EEsProfile || version >= 330; +} + +} // anonymous namespace + +namespace glslc { +bool CompilationContext::CompileShader(const std::string& input_file, + EShLanguage shader_stage) { + GlslInitializer initializer; + + std::vector input_data; + if (!shaderc_util::ReadFile(input_file, &input_data)) { + return false; + } + // glslang expects null-terminated strings even though it then uses + // data/length internally. + // TODO(awoloszyn): Once we add data/length into glslang, fix this. + input_data.push_back('\0'); + + const std::string output_file = GetOutputFileName(input_file); + string_piece error_output_file_name(input_file); + if (input_file == "-") { + // If the input file was stdin, we want to output errors as . + error_output_file_name = ""; + } + + const std::string macro_definitions = + shaderc_util::format(predefined_macros_, "#define ", " ", "\n"); + + std::string preprocessed_shader; + + // If it is preprocess_only_, we definitely need to preprocess. Otherwise, if + // we don't know the stage until now, we need the preprocessed shader to + // deduce the shader stage. + if (preprocess_only_ || shader_stage == EShLangCount) { + bool success; + std::string glslang_errors; + std::tie(success, preprocessed_shader, glslang_errors) = + PreprocessShader(input_file, input_data, macro_definitions); + + success &= PrintFilteredErrors(error_output_file_name, warnings_as_errors_, + /* suppress_warnings = */ true, + glslang_errors.c_str(), &total_warnings_, + &total_errors_); + if (!success) return false; + + if (preprocess_only_) { + return shaderc_util::WriteFile(output_file, + string_piece(preprocessed_shader)); + } else { + std::string errors; + std::tie(shader_stage, errors) = + DeduceShaderStage(input_file, preprocessed_shader); + + if (shader_stage == EShLangCount) { + std::cerr << errors; + return false; + } + } + } + + glslang::TShader shader(shader_stage); + const char* shader_strings = &input_data[0]; + shader.setStrings(&shader_strings, 1); + shader.setPreamble(macro_definitions.c_str()); + + // TODO(dneto): Generate source-level debug info if requested. + bool success = shader.parse( + &shaderc_util::kDefaultTBuiltInResource, default_version_, + default_profile_, force_version_profile_, kNotForwardCompatible, + EShMsgDefault, glslc::FileIncluder(&include_file_finder_)); + + success &= PrintFilteredErrors(error_output_file_name, warnings_as_errors_, + suppress_warnings_, shader.getInfoLog(), + &total_warnings_, &total_errors_); + if (!success) return false; + + glslang::TProgram program; + program.addShader(&shader); + success = program.link(EShMsgDefault); + success &= PrintFilteredErrors(error_output_file_name, warnings_as_errors_, + suppress_warnings_, program.getInfoLog(), + &total_warnings_, &total_errors_); + if (!success) return false; + + std::vector spirv; + glslang::GlslangToSpv(*program.getIntermediate(shader_stage), spirv); + if (disassemble_) { + spv::Parameterize(); + GLSL_STD_450::GetDebugNames(GlslStd450DebugNames); + std::ostringstream disassembled_spirv; + spv::Disassemble(disassembled_spirv, spirv); + return shaderc_util::WriteFile(output_file, disassembled_spirv.str()); + } else { + return shaderc_util::WriteFile( + output_file, string_piece(reinterpret_cast(spirv.data()), + reinterpret_cast(&spirv.back()) + + sizeof(uint32_t))); + } +} + +void CompilationContext::AddIncludeDirectory(const std::string& path) { + include_file_finder_.search_path().push_back(path); +} + +void CompilationContext::AddMacroDefinition(const string_piece& macro, + const string_piece& definition) { + predefined_macros_[macro] = definition; +} + +void CompilationContext::SetForcedVersionProfile(int version, + EProfile profile) { + default_version_ = version; + default_profile_ = profile; + force_version_profile_ = true; +} +void CompilationContext::SetIndividualCompilationMode() { + if (!disassemble_) { + needs_linking_ = false; + file_extension_ = ".spv"; + } +} +void CompilationContext::SetDisassemblyMode() { + disassemble_ = true; + needs_linking_ = false; + file_extension_ = ".s"; +} +void CompilationContext::SetPreprocessingOnlyMode() { + preprocess_only_ = true; + needs_linking_ = false; + if (output_file_name_.empty()) { + output_file_name_ = "-"; + } +} +void CompilationContext::SetWarningsAsErrors() { warnings_as_errors_ = true; } + +void CompilationContext::SetGenerateDebugInfo() { generate_debug_info_ = true; } + +void CompilationContext::SetSuppressWarnings() { suppress_warnings_ = true; } + +bool CompilationContext::ValidateOptions(size_t num_files) { + if (num_files == 0) { + std::cerr << "glslc: error: no input files" << std::endl; + return false; + } + + if (num_files > 1 && needs_linking_) { + std::cerr << "glslc: error: linking multiple files is not supported yet. " + "Use -c to compile files individually." + << std::endl; + return false; + } + + // If we are outputting many object files, we cannot specify -o. Also + // if we are preprocessing multiple files they must be to stdout. + if (num_files > 1 && + ((!preprocess_only_ && !needs_linking_ && !output_file_name_.empty()) || + (preprocess_only_ && output_file_name_ != "-"))) { + std::cerr << "glslc: error: cannot specify -o when generating multiple" + " output files" + << std::endl; + return false; + } + return true; +} + +void CompilationContext::OutputMessages() { + glslc::OutputMessages(total_warnings_, total_errors_); +} + +std::tuple CompilationContext::PreprocessShader( + const std::string& filename, const std::vector& shader_source, + const std::string& shader_preamble) { + const glslc::FileIncluder includer(&include_file_finder_); + + // The stage does not matter for preprocessing. + glslang::TShader shader(EShLangVertex); + const char* shader_strings = &shader_source[0]; + shader.setStrings(&shader_strings, 1); + shader.setPreamble(shader_preamble.c_str()); + + std::string preprocessed_shader; + const bool success = shader.preprocess( + &shaderc_util::kDefaultTBuiltInResource, default_version_, + default_profile_, force_version_profile_, kNotForwardCompatible, + EShMsgOnlyPreprocessor, &preprocessed_shader, includer); + + if (success) { + return std::make_tuple(true, preprocessed_shader, shader.getInfoLog()); + } + return std::make_tuple(false, "", shader.getInfoLog()); +} + +std::string CompilationContext::GetOutputFileName(std::string input_filename) { + std::string output_file = "a.spv"; + if (!needs_linking_) { + if (IsStageFile(input_filename)) { + output_file = input_filename + file_extension_; + } else { + output_file = input_filename.substr(0, input_filename.find_last_of('.')) + + file_extension_; + } + } + if (!output_file_name_.empty()) output_file = output_file_name_.str(); + return output_file; +} + +std::pair CompilationContext::DeduceShaderStage( + const std::string& filename, const std::string& preprocessed_shader) { + EShLanguage stage; + std::string errors; + std::string glslang_errors; + std::tie(stage, errors) = + GetShaderStageFromSourceCode(filename, preprocessed_shader); + if (stage == EShLangCount) { + if (errors.empty()) { + stage = GetShaderStageFromFileName(filename); + if (stage == EShLangCount) { + errors = "glslc: error: '" + filename + "': "; + if (IsGlslFile(filename)) { + errors += + ".glsl file encountered but no -fshader-stage specified ahead"; + } else if (filename == "-") { + errors += + "-fshader-stage required when input is from standard input \"-\""; + } else { + errors += "file not recognized: File format not recognized"; + } + errors += "\n"; + return std::make_pair(EShLangCount, errors); + } + } else { + return std::make_pair(EShLangCount, errors); + } + } + return std::make_pair(stage, errors); +} + +std::pair +CompilationContext::GetShaderStageFromSourceCode( + const std::string& filename, const std::string& preprocessed_shader) { + const string_piece kPragmaShaderStageDirective = "#pragma shader_stage"; + const string_piece kLineDirective = "#line"; + + int version; + EProfile profile; + std::tie(version, profile) = DeduceVersionProfile(preprocessed_shader); + const bool is_for_next_line = LineDirectiveIsForNextLine(version, profile); + + std::vector lines = + string_piece(preprocessed_shader).get_fields('\n'); + // The logical line number, which starts from 1 and is sensitive to #line + // directives, and stage value for #pragma shader_stage() directives. + std::vector> stages; + // The physical line numbers of the first #pragma shader_stage() line and + // first non-preprocessing line in the preprocessed shader text. + size_t first_pragma_shader_stage = lines.size() + 1; + size_t first_non_pp_line = lines.size() + 1; + + for (size_t i = 0, logical_line_no = 1; i < lines.size(); ++i) { + const string_piece current_line = lines[i].strip_whitespace(); + if (current_line.starts_with(kPragmaShaderStageDirective)) { + const string_piece stage_value = + current_line.substr(kPragmaShaderStageDirective.size()).strip("()"); + stages.emplace_back(logical_line_no, stage_value); + first_pragma_shader_stage = std::min(first_pragma_shader_stage, i + 1); + } else if (!current_line.empty() && !current_line.starts_with("#")) { + first_non_pp_line = std::min(first_non_pp_line, i + 1); + } + + // Update logical line number for the next line. + if (current_line.starts_with(kLineDirective)) { + // Note that for core profile, the meaning of #line changed since version + // 330. The line number given by #line used to mean the logical line + // number of the #line line. Now it means the logical line number of the + // next line after the #line line. + logical_line_no = + std::atoi(current_line.substr(kLineDirective.size()).data()) + + (is_for_next_line ? 0 : 1); + } else { + ++logical_line_no; + } + } + if (stages.empty()) return std::make_pair(EShLangCount, ""); + + std::string error_message; + + // TODO(antiagainst): #line could change the effective filename once we add + // support for filenames in #line directives. + + if (first_pragma_shader_stage > first_non_pp_line) { + error_message += filename + ":" + std::to_string(stages[0].first) + + ": error: '#pragma': the first 'shader_stage' #pragma " + "must appear before any non-preprocessing code\n"; + } + + EShLanguage stage = MapStageNameToLanguage(stages[0].second); + if (stage == EShLangCount) { + error_message += + filename + ":" + std::to_string(stages[0].first) + + ": error: '#pragma': invalid stage for 'shader_stage' #pragma: '" + + stages[0].second.str() + "'\n"; + } + + for (size_t i = 1; i < stages.size(); ++i) { + if (stages[i].second != stages[0].second) { + error_message += filename + ":" + std::to_string(stages[i].first) + + ": error: '#pragma': conflicting stages for " + "'shader_stage' #pragma: '" + + stages[i].second.str() + "' (was '" + + stages[0].second.str() + "' at " + filename + ":" + + std::to_string(stages[0].first) + ")\n"; + } + } + + return std::make_pair(error_message.empty() ? stage : EShLangCount, + error_message); +} + +std::pair CompilationContext::DeduceVersionProfile( + const std::string& preprocessed_shader) { + int version = default_version_; + EProfile profile = default_profile_; + if (!force_version_profile_) { + std::tie(version, profile) = + GetVersionProfileFromSourceCode(preprocessed_shader); + if (version == 0 && profile == ENoProfile) { + version = default_version_; + profile = default_profile_; + } + } + return std::make_pair(version, profile); +} + +std::pair CompilationContext::GetVersionProfileFromSourceCode( + const std::string& preprocessed_shader) { + string_piece pound_version = preprocessed_shader; + const size_t pound_version_loc = pound_version.find("#version"); + if (pound_version_loc == string_piece::npos) { + return std::make_pair(0, ENoProfile); + } + pound_version = + pound_version.substr(pound_version_loc + std::strlen("#version")); + pound_version = pound_version.substr(0, pound_version.find_first_of("\n")); + + std::string version_profile; + for (const auto character : pound_version) { + if (character != ' ') version_profile += character; + } + + int version; + EProfile profile; + if (!glslc::GetVersionProfileFromCmdLine(version_profile, &version, + &profile)) { + return std::make_pair(0, ENoProfile); + } + return std::make_pair(version, profile); +} + +} // namesapce glslc diff --git a/glslc/src/compilation_context.h b/glslc/src/compilation_context.h new file mode 100644 index 000000000..152c8268e --- /dev/null +++ b/glslc/src/compilation_context.h @@ -0,0 +1,189 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef GLSLC_COMPILATION_CONTEXT_H +#define GLSLC_COMPILATION_CONTEXT_H + +#include +#include +#include + +#include "glslang/Public/ShaderLang.h" + +#include "libshaderc_util/file_finder.h" +#include "libshaderc_util/string_piece.h" + +#include "file_includer.h" + +namespace glslc { + +// Initializes glslang on creation, and destroys it on completion. +class GlslInitializer { + public: + GlslInitializer() { glslang::InitializeProcess(); } + ~GlslInitializer() { glslang::FinalizeProcess(); } +}; + +// Maps macro names to their definitions. Stores string_pieces, so the +// underlying strings must outlive it. +using MacroDictionary = + std::unordered_map; + +class CompilationContext { + public: + CompilationContext() + : default_version_(110), + default_profile_(ENoProfile), + warnings_as_errors_(false), + disassemble_(false), + force_version_profile_(false), + preprocess_only_(false), + needs_linking_(true), + generate_debug_info_(false), + suppress_warnings_(false), + total_warnings_(0), + total_errors_(0) {} + + // Compiles a shader received in input_file, and places the output + // into the file named by output_file_name_ member. The shader is assumed to + // be for shader_stage stage. If force_version_profile_ is set, the shader's + // version/profile is forced to be default_version_/default_profile_ + // regardless of the #version directive in the source code. The + // total_warnings_ and total_errors_ members will accumulate the + // total number of warnings and errors found in this shader. + // Will output errors to std::cerr. + // Returns true on success and false otherwise. + bool CompileShader(const std::string& input_file, EShLanguage shader_stage); + + // Adds a directory to be searched when processing #include directives. + // Add an empty string to allow the resolution of absolute paths as well + // as paths relative to the current working directory. + void AddIncludeDirectory(const std::string& path); + + // Adds a macro definition to be used when processing #define directives. + void AddMacroDefinition(const shaderc_util::string_piece& macro, + const shaderc_util::string_piece& definition); + + // Forces the default version and profile. No sanity check + // for default_version_ and default_profile_ is performed. + void SetForcedVersionProfile(int version, EProfile profile); + + // Sets the output filename. A name of "-" indicates standard output. + void SetOutputFileName(const shaderc_util::string_piece& file) { + output_file_name_ = file; + } + + // When any files are to be compiled, they are compiled individually + // and written to separate output files instead of linked together. + void SetIndividualCompilationMode(); + + // Instead of outputting object files, output the disassembled + // textual output. + void SetDisassemblyMode(); + + // Instead of outputting object files, output the preprocessed + // source files. + void SetPreprocessingOnlyMode(); + + // Requests that the compiler places debug information into + // the object code, such as identifier names and line numbers. + void SetGenerateDebugInfo(); + + // When a warning is encountered it treat it as an error. + void SetWarningsAsErrors(); + + // Any warning message generated is suppressed before it is output. + void SetSuppressWarnings(); + + // Returns false if any options are incompatible. + // The num_files parameter represents the number of files that will + // be compiled. + bool ValidateOptions(size_t num_files); + + // Outputs the number of warnings and errors if there are any. + void OutputMessages(); + + private: + // Preprocesses a shader whose filename is filename and content is + // shader_source. If preprocessing is successful, returns true, the + // preprocessed shader, and any warning message as a tuple. Otherwise, + // returns false, an empty string, and error messages as a tuple. + // + // The filename parameter is the input shader's filename. + // The shader_source parameter is the input shader's source text. + // The shader_preamble parameter is a context-specific preamble internally + // prepended to shader_text without affecting the validity of its #version + // position. + // + // If force_version_profile_ is set, the shader's version/profile is forced + // to be default_version_/default_profile_ regardless of the #version + // directive in the source code. + std::tuple PreprocessShader( + const std::string& filename, const std::vector& shader_source, + const std::string& shader_preamble); + + // Returns the name of the output file, given the input_filename string. + std::string GetOutputFileName(std::string input_filename); + + // Determines the shader stage from pragmas embedded in the source text or + // from the filename extension, if possible. If errors occur, returns an + // errors string in the second element of the pair and returns EShLangCount + // in the first element. + std::pair DeduceShaderStage( + const std::string& filename, const std::string& preprocessed_shader); + + // Determines the shader stage from pragmas embedded in the source text if + // possible. In the returned pair, the glslang EShLanguage is the shader + // stage deduced. If no #pragma directives for shader stage exist, it's + // EShLangCount. If errors occur, the second element in the pair is the + // error message. Otherwise, it's an empty string. + std::pair GetShaderStageFromSourceCode( + const std::string& filename, const std::string& preprocessed_shader); + + // Determines version and profile from command line, or the source code. + // Returns the decoded version and profile pair on success. Otherwise, + // returns (0, ENoProfile). + std::pair DeduceVersionProfile( + const std::string& preprocessed_shader); + + // Gets version and profile specification from the given preprocessedshader. + // Returns the decoded version and profile pair on success. Otherwise, + // returns (0, ENoProfile). + std::pair GetVersionProfileFromSourceCode( + const std::string& preprocessed_shader); + + // The default version for glsl is 110, or 100 if you are using an + // es profile. But we want to default to a non-es profile. + int default_version_; + + EProfile default_profile_; + bool warnings_as_errors_; + bool disassemble_; + bool force_version_profile_; + bool preprocess_only_; + bool needs_linking_; + bool generate_debug_info_; + bool suppress_warnings_; + MacroDictionary predefined_macros_; + shaderc_util::FileFinder include_file_finder_; + + // This is set by the various Set*Mode functions. It is set to reflect the + // type of file being generated. + std::string file_extension_; + shaderc_util::string_piece output_file_name_; + size_t total_warnings_; + size_t total_errors_; +}; +} +#endif // GLSLC_COMPILATION_CONTEXT_H diff --git a/glslc/src/file.cc b/glslc/src/file.cc new file mode 100644 index 000000000..0d1b74aac --- /dev/null +++ b/glslc/src/file.cc @@ -0,0 +1,26 @@ +// Copyright 2015 The Shaderc 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. + +#include "file.h" + +namespace glslc { + +shaderc_util::string_piece GetFileExtension( + const shaderc_util::string_piece& filename) { + size_t dot_pos = filename.find_last_of("."); + if (dot_pos == shaderc_util::string_piece::npos) return ""; + return filename.substr(dot_pos + 1); +} + +} // namespace glslc diff --git a/glslc/src/file.h b/glslc/src/file.h new file mode 100644 index 000000000..93d98ef67 --- /dev/null +++ b/glslc/src/file.h @@ -0,0 +1,42 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef GLSLC_FILE_H_ +#define GLSLC_FILE_H_ + +#include "libshaderc_util/string_piece.h" + +namespace glslc { + +// Given a file name, returns its extension. If no extension exists, +// returns an empty string_piece. +shaderc_util::string_piece GetFileExtension( + const shaderc_util::string_piece& filename); + +// Returns true if the given file name ends with a known shader file extension. +inline bool IsStageFile(const shaderc_util::string_piece& filename) { + const shaderc_util::string_piece extension = + glslc::GetFileExtension(filename); + return extension == "vert" || extension == "frag" || extension == "tesc" || + extension == "tese" || extension == "geom" || extension == "comp"; +} + +// Returns true if the given file name has extension "glsl". +inline bool IsGlslFile(const shaderc_util::string_piece& filename) { + return glslc::GetFileExtension(filename) == "glsl"; +} + +} // namespace glslc + +#endif // GLSLC_FILE_H_ diff --git a/glslc/src/file_includer.cc b/glslc/src/file_includer.cc new file mode 100644 index 000000000..ed45340b3 --- /dev/null +++ b/glslc/src/file_includer.cc @@ -0,0 +1,32 @@ +// Copyright 2015 The Shaderc 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. + +#include "file_includer.h" + +#include "libshaderc_util/io.h" + +namespace glslc { + +std::pair FileIncluder::include(const char* filename) const { + const std::string full_path = file_finder_.FindReadableFilepath(filename); + std::vector content; + if (!full_path.empty() && shaderc_util::ReadFile(full_path, &content)) { + return std::make_pair(true, std::string(content.begin(), content.end())); + } else { + // TODO(deki): Emit error message. + return std::make_pair(false, std::string("")); + } +} + +} // namespace glslc diff --git a/glslc/src/file_includer.h b/glslc/src/file_includer.h new file mode 100644 index 000000000..1d1437b0b --- /dev/null +++ b/glslc/src/file_includer.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef GLSLC_FILE_INCLUDER_H_ +#define GLSLC_FILE_INCLUDER_H_ + +#include +#include + +#include "glslang/Public/ShaderLang.h" + +#include "libshaderc_util/file_finder.h" + +namespace glslc { + +// An Includer for files. Translates the #include argument into the file's +// contents, based on a FileFinder. +class FileIncluder : public glslang::TShader::Includer { + public: + FileIncluder(const shaderc_util::FileFinder* file_finder) + : file_finder_(*file_finder) {} + + // Finds filename in search path and returns its contents. See + // Includer::include(). + std::pair include(const char* filename) const override; + + private: + // Used by include() to get the full filepath. + const shaderc_util::FileFinder& file_finder_; +}; + +} // namespace glslc + +#endif // GLSLC_FILE_INCLUDER_H_ diff --git a/glslc/src/file_test.cc b/glslc/src/file_test.cc new file mode 100644 index 000000000..f242621df --- /dev/null +++ b/glslc/src/file_test.cc @@ -0,0 +1,87 @@ +// Copyright 2015 The Shaderc 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. + +#include "file.h" + +#include + +namespace { + +using glslc::GetFileExtension; +using glslc::IsStageFile; +using glslc::IsGlslFile; +using shaderc_util::string_piece; + +class FileExtensionTest : public testing::Test { + protected: + string_piece empty = ""; + string_piece dot = "."; + string_piece no_ext = "shader"; + string_piece trailing_dot = "shader."; + string_piece vert_ext = "shader.vert"; + string_piece frag_ext = "shader.frag"; + string_piece tesc_ext = "shader.tesc"; + string_piece tese_ext = "shader.tese"; + string_piece geom_ext = "shader.geom"; + string_piece comp_ext = "shader.comp"; + string_piece glsl_ext = "shader.glsl"; + string_piece multi_dot = "shader.some..ext"; +}; + +TEST_F(FileExtensionTest, GetFileExtension) { + EXPECT_EQ("", GetFileExtension(empty)); + EXPECT_EQ("", GetFileExtension(dot)); + EXPECT_EQ("", GetFileExtension(no_ext)); + EXPECT_EQ("", GetFileExtension(trailing_dot)); + EXPECT_EQ("vert", GetFileExtension(vert_ext)); + EXPECT_EQ("frag", GetFileExtension(frag_ext)); + EXPECT_EQ("tesc", GetFileExtension(tesc_ext)); + EXPECT_EQ("tese", GetFileExtension(tese_ext)); + EXPECT_EQ("geom", GetFileExtension(geom_ext)); + EXPECT_EQ("comp", GetFileExtension(comp_ext)); + EXPECT_EQ("glsl", GetFileExtension(glsl_ext)); + EXPECT_EQ("ext", GetFileExtension(multi_dot)); +} + +TEST_F(FileExtensionTest, IsGlslFile) { + EXPECT_FALSE(IsGlslFile(empty)); + EXPECT_FALSE(IsGlslFile(dot)); + EXPECT_FALSE(IsGlslFile(no_ext)); + EXPECT_FALSE(IsGlslFile(trailing_dot)); + EXPECT_FALSE(IsGlslFile(vert_ext)); + EXPECT_FALSE(IsGlslFile(frag_ext)); + EXPECT_FALSE(IsGlslFile(tesc_ext)); + EXPECT_FALSE(IsGlslFile(tese_ext)); + EXPECT_FALSE(IsGlslFile(geom_ext)); + EXPECT_FALSE(IsGlslFile(comp_ext)); + EXPECT_TRUE(IsGlslFile(glsl_ext)); + EXPECT_FALSE(IsGlslFile(multi_dot)); +} + +TEST_F(FileExtensionTest, IsStageFile) { + EXPECT_FALSE(IsStageFile(empty)); + EXPECT_FALSE(IsStageFile(dot)); + EXPECT_FALSE(IsStageFile(no_ext)); + EXPECT_FALSE(IsStageFile(trailing_dot)); + EXPECT_TRUE(IsStageFile(vert_ext)); + EXPECT_TRUE(IsStageFile(frag_ext)); + EXPECT_TRUE(IsStageFile(tesc_ext)); + EXPECT_TRUE(IsStageFile(tese_ext)); + EXPECT_TRUE(IsStageFile(geom_ext)); + EXPECT_TRUE(IsStageFile(comp_ext)); + EXPECT_FALSE(IsStageFile(glsl_ext)); + EXPECT_FALSE(IsStageFile(multi_dot)); +} + +} // anonymous namespace diff --git a/glslc/src/main.cc b/glslc/src/main.cc new file mode 100644 index 000000000..068f50d38 --- /dev/null +++ b/glslc/src/main.cc @@ -0,0 +1,230 @@ +// Copyright 2015 The Shaderc 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. + +#include +#include +#include +#include +#include +#include +#include + +#include "libshaderc_util/string_piece.h" + +#include "compilation_context.h" +#include "file.h" +#include "shader_stage.h" +#include "version_profile.h" + +using shaderc_util::string_piece; + +namespace { + +// Prints the help message. +void PrintHelp(std::ostream* out) { + *out << "Usage: glslc [options] file...\n\n" + << "An input file of - represents standard input.\n\n" + << "Options:\n" + << " -c Only run preprocess, compile, and assemble" + << " steps.\n" + << " -Dmacro[=defn] Add an implicit macro definition.\n" + << " -E Outputs only the results of the preprocessing" + << " step.\n" + << " Output defaults to standard out.\n" + << " -fshader-stage=\n" + << " Treat subsequent input files as having stage " + << ".\n" + << " Valid stages are vertex, fragment, tesscontrol, " + << "tesseval,\n" + << " geometry, and compute.\n" + << " -g Generate source-level debug information.\n" + << " Currently this option has no effect.\n" + << " --help Display available options.\n" + << " -I Add directory to include search path.\n" + << " -o Write output to .\n" + << " A file name of '-' represents standard output.\n" + << " -std= Version and profile for input files. Possible " + << "values\n" + << " are concatenations of version and profile, e.g. " + << "310es,\n" + << " 450core, etc.\n" + << " -S Only run preprocess and compilation steps.\n" + << " -w Suppresses all warning messages.\n" + << " -Werror Treat all warnings as errors.\n" + << " -x Treat subsequent input files as having type " + << ".\n" + << " The only supported language is glsl." + << std::endl; +} + +// Parses the argument for the option at index in argv. On success, returns +// true and writes the parsed argument into option_argument. Returns false +// if any error occurs. option is the expected option at argv[index]. +// After calling this function, index will be at the last command line +// argument consumed. +bool GetOptionArgument(int argc, char** argv, int* index, + const std::string& option, + string_piece* option_argument) { + const string_piece arg = argv[*index]; + assert(arg.starts_with(option)); + if (arg.size() != option.size()) { + *option_argument = arg.substr(option.size()); + return true; + } else { + if (++(*index) >= argc) { + return false; + } else { + *option_argument = argv[*index]; + return true; + } + } +} + +} // anonymous namespace + +int main(int argc, char** argv) { + std::vector> input_files; + EShLanguage force_shader_stage = EShLangCount; + glslc::CompilationContext context; + bool success = true; + bool has_stdin_input = false; + + context.AddIncludeDirectory(""); + + for (int i = 1; i < argc; ++i) { + const string_piece arg = argv[i]; + if (arg == "--help") { + ::PrintHelp(&std::cout); + return 0; + } else if (arg == "-c") { + context.SetIndividualCompilationMode(); + } else if (arg == "-g") { + context.SetGenerateDebugInfo(); + } else if (arg == "-S") { + context.SetDisassemblyMode(); + } else if (arg == "-E") { + context.SetPreprocessingOnlyMode(); + } else if (arg == "-Werror") { + context.SetWarningsAsErrors(); + } else if (arg == "-w") { + context.SetSuppressWarnings(); + } else if (arg.starts_with("-o")) { + string_piece file_name; + if (!GetOptionArgument(argc, argv, &i, "-o", &file_name)) { + std::cerr + << "glslc: error: argument to '-o' is missing (expected 1 value)" + << std::endl; + return 1; + } + context.SetOutputFileName(file_name); + } else if (arg.starts_with("-x")) { + string_piece option_arg; + if (!GetOptionArgument(argc, argv, &i, "-x", &option_arg)) { + std::cerr + << "glslc: error: argument to '-x' is missing (expected 1 value)" + << std::endl; + success = false; + } else { + if (option_arg != "glsl") { + std::cerr << "glslc: error: language not recognized: '" << option_arg + << "'" << std::endl; + return 1; + } + } + } else if (arg.starts_with("-fshader-stage=")) { + const string_piece stage = arg.substr(std::strlen("-fshader-stage=")); + force_shader_stage = glslc::GetShaderStageFromCmdLine(arg); + if (force_shader_stage == EShLangCount) { + std::cerr << "glslc: error: stage not recognized: '" << stage << "'" + << std::endl; + return 1; + } + } else if (arg.starts_with("-D")) { + const size_t length = arg.size(); + if (length <= 2) { + std::cerr << "glslc: error: argument to '-D' is missing" << std::endl; + } else { + const string_piece argument = arg.substr(2); + size_t name_length = argument.find_first_of('='); + const string_piece name = argument.substr(0, name_length); + if (name.starts_with("GL_")) { + std::cerr + << "glslc: error: names beginning with 'GL_' cannot be defined: " + << arg << std::endl; + return 1; + } + if (name.find("__") != string_piece::npos) { + std::cerr + << "glslc: warning: names containing consecutive underscores " + "are reserved: " + << arg << std::endl; + } + // TODO(deki): check arg for newlines. + context.AddMacroDefinition(name, name_length < argument.size() + ? argument.substr(name_length + 1) + : ""); + } + } else if (arg.starts_with("-std=")) { + const string_piece standard = arg.substr(std::strlen("-std=")); + int version; + EProfile profile; + if (!glslc::GetVersionProfileFromCmdLine(standard.str(), &version, + &profile)) { + std::cerr << "glslc: error: invalid value '" << standard + << "' in '-std=" << standard << "'" << std::endl; + return 1; + } + context.SetForcedVersionProfile(version, profile); + } else if (arg.starts_with("-I")) { + string_piece option_arg; + if (!GetOptionArgument(argc, argv, &i, "-I", &option_arg)) { + std::cerr + << "glslc: error: argument to '-I' is missing (expected 1 value)" + << std::endl; + success = false; + } else { + context.AddIncludeDirectory(option_arg.str()); + } + } else if (!(arg == "-") && arg[0] == '-') { + std::cerr << "glslc: error: " + << (arg[1] == '-' ? "unsupported option" : "unknown argument") + << ": '" << arg << "'" << std::endl; + return 1; + } else { + if (arg == "-") { + if (has_stdin_input) { + std::cerr << "glslc: error: specifying standard input \"-\" as input " + << "more than once is not allowed." << std::endl; + return 1; + } + has_stdin_input = true; + } + input_files.emplace_back(arg.str(), force_shader_stage); + } + } + + if (!context.ValidateOptions(input_files.size())) return 1; + + if (!success) return 1; + + for (const auto& input_file : input_files) { + const std::string& name = input_file.first; + const EShLanguage stage = input_file.second; + + success &= context.CompileShader(name, stage); + } + + context.OutputMessages(); + return success ? 0 : 1; +} diff --git a/glslc/src/message.cc b/glslc/src/message.cc new file mode 100644 index 000000000..770c45437 --- /dev/null +++ b/glslc/src/message.cc @@ -0,0 +1,244 @@ +// Copyright 2015 The Shaderc 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. + +#include "message.h" + +#include +#include +#include +#include + +namespace glslc { + +using shaderc_util::string_piece; + +namespace { + +// Given a message, deduces and returns its type. If the message type is +// recognized, advances *message past the prefix indicating the type. Otherwise, +// leaves *message unchanged and returns MessageType::Unknown. +MessageType DeduceMessageType(string_piece* message) { + static const char kErrorMessage[] = "ERROR: "; + static const char kWarningMessage[] = "WARNING: "; + static const char kGlobalWarningMessage[] = "Warning, "; + + if (message->starts_with(kErrorMessage)) { + *message = message->substr(::strlen(kErrorMessage)); + return MessageType::Error; + } else if (message->starts_with(kWarningMessage)) { + *message = message->substr(::strlen(kWarningMessage)); + return MessageType::Warning; + } else if (message->starts_with(kGlobalWarningMessage)) { + *message = message->substr(::strlen(kGlobalWarningMessage)); + return MessageType::GlobalWarning; + } + return MessageType::Unknown; +} + +// Deduces a location specification from the given message. A location +// specification is of the form "::". If the deduction +// is successful, returns true and updates source_name and line_number to the +// deduced source name and line numer respectively. The prefix standing for the +// location specification in message is skipped. Otherwise, returns false and +// keeps all parameters untouched. +bool DeduceLocationSpec(string_piece* message, string_piece* source_name, + string_piece* line_number) { + string_piece rest(*message); + const size_t first_colon_pos = rest.find_first_of(':'); + if (first_colon_pos == string_piece::npos) return false; + const string_piece source = rest.substr(0, first_colon_pos); + + rest = rest.substr(first_colon_pos + 1); + // TODO(antiagainst): we use ':' as a delimiter here. It may be a valid + // character in the filename. Also be aware of other special characters, + // for example, ' '. + const size_t second_colon_pos = rest.find_first_of(':'); + if (second_colon_pos == string_piece::npos) return false; + const string_piece line = rest.substr(0, second_colon_pos); + + if (!std::all_of(line.begin(), line.end(), ::isdigit)) return false; + + *source_name = source; + *line_number = line; + *message = rest.substr(second_colon_pos + 1).strip_whitespace(); + return true; +} + +// Returns true if the given message is a summary message. +bool IsSummaryMessage(const string_piece& message) { + const size_t space_loc = message.find_first_of(' '); + if (space_loc == string_piece::npos) return false; + const string_piece number = message.substr(0, space_loc); + const string_piece rest = message.substr(space_loc + 1); + if (!std::all_of(number.begin(), number.end(), ::isdigit)) return false; + if (!rest.starts_with("compilation errors.")) return false; + return true; +} + +} // anonymous namespace + +MessageType ParseGlslangOutput(const string_piece& message, + bool warnings_as_errors, bool suppress_warnings, + string_piece* source_name, + string_piece* line_number, string_piece* rest) { + string_piece rest_of_message(message); + source_name->clear(); + line_number->clear(); + rest->clear(); + + // The glslang warning/error messages are typically of the following form: + // + // + // can be "WARNING:", "ERROR:", or "Warning, ". "WARNING:" + // means a warning message for a certain line, while "Warning, " means a + // global one. + // + // is of the form: + // :: + // It doesn't exist if the warning/error message is a global one. + + bool is_error = false; + + // Handle . + switch (DeduceMessageType(&rest_of_message)) { + case MessageType::Warning: + if (suppress_warnings) return MessageType::Ignored; + break; + case MessageType::Error: + is_error = true; + break; + case MessageType::GlobalWarning: + if (suppress_warnings) return MessageType::Ignored; + *rest = rest_of_message; + return warnings_as_errors ? MessageType::GlobalError + : MessageType::GlobalWarning; + case MessageType::Unknown: + *rest = rest_of_message; + return MessageType::Unknown; + default: + break; + } + + rest_of_message = rest_of_message.strip_whitespace(); + if (rest_of_message.empty()) return MessageType::Unknown; + + // Now we have stripped the . Try to see if we can find + // a . + if (DeduceLocationSpec(&rest_of_message, source_name, line_number)) { + *rest = rest_of_message; + return (is_error || warnings_as_errors) ? MessageType::Error + : MessageType::Warning; + } else { + // No . This is a global warning/error message. + // A special kind of global message is summary message, which should + // start with a number. + *rest = rest_of_message; + if (IsSummaryMessage(rest_of_message)) { + return (is_error || warnings_as_errors) ? MessageType::ErrorSummary + : MessageType::WarningSummary; + } + return (is_error || warnings_as_errors) ? MessageType::GlobalError + : MessageType::GlobalWarning; + } + return MessageType::Unknown; +} + +bool PrintFilteredErrors(const string_piece& file_name, bool warnings_as_errors, + bool suppress_warnings, const char* error_list, + size_t* total_warnings, size_t* total_errors) { + const char* ignored_error_strings[] = { + "Warning, version 310 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Warning, version 400 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Warning, version 410 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Warning, version 420 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Warning, version 430 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Warning, version 440 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Warning, version 450 is not yet complete; most version-specific " + "features are present, but some are missing.", + "Linked vertex stage:", "Linked fragment stage:", + "Linked tessellation control stage:", + "Linked tessellation evaluation stage:", "Linked geometry stage:", + "Linked compute stage:", ""}; + size_t existing_total_errors = *total_errors; + string_piece error_messages(error_list); + for (const string_piece& message : error_messages.get_fields('\n')) { + if (std::find(std::begin(ignored_error_strings), + std::end(ignored_error_strings), + message) == std::end(ignored_error_strings)) { + string_piece source_name; + string_piece line_number; + string_piece rest; + switch (MessageType type = ParseGlslangOutput( + message, warnings_as_errors, suppress_warnings, &source_name, + &line_number, &rest)) { + case MessageType::Error: + case MessageType::Warning: + std::cerr << file_name << ":"; + assert(!source_name.empty() && !line_number.empty() && !rest.empty()); + std::cerr << line_number << ": " + << (type == MessageType::Error ? "error: " : "warning: ") + << rest.strip_whitespace() << std::endl; + *total_errors += type == MessageType::Error; + *total_warnings += type == MessageType::Warning; + break; + case MessageType::ErrorSummary: + case MessageType::WarningSummary: + break; + case MessageType::GlobalError: + case MessageType::GlobalWarning: + assert(!rest.empty()); + *total_errors += type == MessageType::GlobalError; + *total_warnings += type == MessageType::GlobalWarning; + std::cerr << file_name << ": " + << (type == MessageType::GlobalError ? "error" : "warning") + << ": " << rest.strip_whitespace() << std::endl; + break; + case MessageType::Unknown: + std::cerr << file_name << ":"; + std::cerr << " " << message << std::endl; + break; + case MessageType::Ignored: + break; + } + } + } + return (existing_total_errors == *total_errors); +} + +// Outputs the number of warnings and errors if there are any. +void OutputMessages(size_t total_warnings, size_t total_errors) { + if (total_warnings > 0 || total_errors > 0) { + if (total_warnings > 0 && total_errors > 0) { + std::cerr << total_warnings << " warning" + << (total_warnings > 1 ? "s" : "") << " and " << total_errors + << " error" << (total_errors > 1 ? "s" : "") << " generated." + << std::endl; + } else if (total_warnings > 0) { + std::cerr << total_warnings << " warning" + << (total_warnings > 1 ? "s" : "") << " generated." + << std::endl; + } else if (total_errors > 0) { + std::cerr << total_errors << " error" << (total_errors > 1 ? "s" : "") + << " generated." << std::endl; + } + } +} + +} // namespace glslc diff --git a/glslc/src/message.h b/glslc/src/message.h new file mode 100644 index 000000000..031a72273 --- /dev/null +++ b/glslc/src/message.h @@ -0,0 +1,79 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef GLSLC_MESSAGE_H_ +#define GLSLC_MESSAGE_H_ + +#include "libshaderc_util/string_piece.h" + +namespace glslc { + +// TODO(antiagainst): document the differences of the following message types. +enum class MessageType { + Warning, + Error, + ErrorSummary, + WarningSummary, + GlobalWarning, + GlobalError, + Unknown, + Ignored +}; + +// Given a glslang warning/error message, processes it in the following way and +// returns its message type. +// +// * Places the source name into the source_name parameter, if found. +// Otherwise, clears the source_name parameter. +// * Places the line number into the line_number parameter, if found. +// Otherwise, clears the line_number parameter. +// * Places the rest of the message (the text past warning/error prefix, source +// name, and line number) into the rest parameter. +// +// If warnings_as_errors is set to true, then all warnings will be treated as +// errors. +// If suppress_warnings is set to true, then no warnings will be emitted. This +// takes precedence over warnings_as_errors. +// +// Examples: +// "ERROR: 0:2: Message" +// source_name="0", line_number="2", rest="Message" +// "Warning, Message" +// source_name="", line_number="", rest="Message" +// "ERROR: 2 errors found." +// source_name="2", line_number="", rest="errors found". +MessageType ParseGlslangOutput(const shaderc_util::string_piece& message, + bool warnings_as_errors, bool suppress_warnings, + shaderc_util::string_piece* source_name, + shaderc_util::string_piece* line_number, + shaderc_util::string_piece* rest); + +// Filters error_messages received from glslang, and outputs any that are not +// ignored in a clang like format. If the warnings_as_errors boolean is set, +// then all warnings will be treated as errors. If the suppress_warnings boolean +// is set then any warning messages are ignored. This takes precedence over +// warnings_as_errors. Increments total_warnings and total_errors based on the +// message types. +// Returns true if no new errors were found when parsing the messages. +bool PrintFilteredErrors(const shaderc_util::string_piece& file_name, + bool warnings_as_errors, bool suppress_warnings, + const char* error_list, size_t* total_warnings, + size_t* total_errors); + +// Outputs the number of warnings and errors if there are any. +void OutputMessages(size_t total_warnings, size_t total_errors); + +} // namespace glslc + +#endif // GLSLC_MESSAGE_H_ diff --git a/glslc/src/message_test.cc b/glslc/src/message_test.cc new file mode 100644 index 000000000..e4d70aa8f --- /dev/null +++ b/glslc/src/message_test.cc @@ -0,0 +1,184 @@ +// Copyright 2015 The Shaderc 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. + +// Some of the tests here check code paths that are not checked by +// integration tests. +// Generally, these would be conditions not generated by the Glslang +// compiler. It's easier to write these unit tests than to inject +// a dependency on a fake compiler. +#include "message.h" + +#include + +using glslc::MessageType; +using glslc::ParseGlslangOutput; +using shaderc_util::string_piece; + +namespace { + +TEST(ParseGlslangOutputTest, EmptyMessageBody) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + EXPECT_EQ(MessageType::Unknown, + ParseGlslangOutput("WARNING: ", false, false, &segment_number, + &line_number, &rest)); + EXPECT_EQ(MessageType::Unknown, + ParseGlslangOutput("ERROR: ", false, false, &segment_number, + &line_number, &rest)); +} + +TEST(ParseGlslangOutputTest, GlobalError) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + EXPECT_EQ( + MessageType::GlobalError, + ParseGlslangOutput("ERROR: too many functions: got 1666473 of them", + false, false, &segment_number, &line_number, &rest)); + EXPECT_EQ("too many functions: got 1666473 of them", rest.str()); + + EXPECT_EQ( + MessageType::GlobalError, + ParseGlslangOutput( + "ERROR: #version: versions before 150 do not allow a profile token", + false, false, &segment_number, &line_number, &rest)); + EXPECT_EQ("#version: versions before 150 do not allow a profile token", + rest.str()); +} + +TEST(ParseGlslangOutputTest, GlobalWarning) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + EXPECT_EQ(MessageType::GlobalWarning, + ParseGlslangOutput("Warning, version 1000 is unknown.", false, + false, &segment_number, &line_number, &rest)); + EXPECT_EQ("version 1000 is unknown.", rest.str()); +} + +TEST(ParseGlslangOutputTest, InvalidSuffixAfterSegmentNumber) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + EXPECT_EQ(MessageType::GlobalWarning, + ParseGlslangOutput("WARNING: 12a", false, false, &segment_number, + &line_number, &rest)); + EXPECT_EQ(MessageType::GlobalError, + ParseGlslangOutput("WARNING: 12a", true, false, &segment_number, + &line_number, &rest)); + EXPECT_EQ(MessageType::GlobalError, + ParseGlslangOutput("ERROR: 42!", false, false, &segment_number, + &line_number, &rest)); +} + +TEST(ParseGlslangOutputTest, OnlyANumber) { + string_piece source_name; + string_piece line_number; + string_piece rest; + EXPECT_EQ(MessageType::GlobalWarning, + ParseGlslangOutput("WARNING: 12", false, false, &source_name, + &line_number, &rest)); + EXPECT_TRUE(source_name.empty()); + EXPECT_TRUE(line_number.empty()); + EXPECT_EQ("12", rest.str()); + + EXPECT_EQ(MessageType::GlobalError, + ParseGlslangOutput("WARNING: 12", true, false, &source_name, + &line_number, &rest)); + EXPECT_TRUE(source_name.empty()); + EXPECT_TRUE(line_number.empty()); + EXPECT_EQ("12", rest.str()); + + EXPECT_EQ(MessageType::GlobalError, + ParseGlslangOutput("ERROR: 42", false, false, &source_name, + &line_number, &rest)); + EXPECT_TRUE(source_name.empty()); + EXPECT_TRUE(line_number.empty()); + EXPECT_EQ("42", rest.str()); +} + +TEST(ParseGlslangOutputTest, InvalidSuffixAfterSegmentNumberColon) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + EXPECT_EQ(MessageType::GlobalWarning, + ParseGlslangOutput("WARNING: 12:0", false, false, &segment_number, + &line_number, &rest)); + EXPECT_EQ(MessageType::GlobalError, + ParseGlslangOutput("ERROR: 42:1234", false, false, &segment_number, + &line_number, &rest)); +} + +TEST(ParseGlslangOutputTest, CompletelyUnrecognized) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + EXPECT_EQ(MessageType::Unknown, + ParseGlslangOutput("hello world!", false, false, &segment_number, + &line_number, &rest)); +} + +TEST(ParseGlslangOutputTest, LocationSpecification) { + string_piece segment_number; + string_piece line_number; + string_piece rest; + + EXPECT_EQ( + MessageType::Error, + ParseGlslangOutput("ERROR: 0:2: '#' : invalid directive: foo", false, + false, &segment_number, &line_number, &rest)); + EXPECT_EQ("0", segment_number.str()); + EXPECT_EQ("2", line_number.str()); + EXPECT_EQ("'#' : invalid directive: foo", rest.str()); + + EXPECT_EQ( + MessageType::Warning, + ParseGlslangOutput("WARNING: 15:36: The following extension must be " + "enabled to use this feature:", + false, false, &segment_number, &line_number, &rest)); + EXPECT_EQ("15", segment_number.str()); + EXPECT_EQ("36", line_number.str()); + EXPECT_EQ("The following extension must be enabled to use this feature:", + rest.str()); +} + +TEST(ParseGlslangOutputTest, FileName) { + string_piece source_name; + string_piece line_number; + string_piece rest; + + EXPECT_EQ(MessageType::Error, + ParseGlslangOutput("ERROR: shader.vert:5: something wrong", false, + false, &source_name, &line_number, &rest)); + EXPECT_EQ("shader.vert", source_name.str()); + EXPECT_EQ("5", line_number.str()); + EXPECT_EQ("something wrong", rest.str()); + + EXPECT_EQ(MessageType::Warning, + ParseGlslangOutput("WARNING: file:42: something wrong", false, + false, &source_name, &line_number, &rest)); + EXPECT_EQ("file", source_name.str()); + EXPECT_EQ("42", line_number.str()); + EXPECT_EQ("something wrong", rest.str()); + + EXPECT_EQ(MessageType::Warning, + ParseGlslangOutput("WARNING: 0xdeedbeef:0: wa:ha:ha", false, false, + &source_name, &line_number, &rest)); + EXPECT_EQ("0xdeedbeef", source_name.str()); + EXPECT_EQ("0", line_number.str()); + EXPECT_EQ("wa:ha:ha", rest.str()); +} + +} // anonymous namespace diff --git a/glslc/src/shader_stage.cc b/glslc/src/shader_stage.cc new file mode 100644 index 000000000..7fd5d0dde --- /dev/null +++ b/glslc/src/shader_stage.cc @@ -0,0 +1,74 @@ +// Copyright 2015 The Shaderc 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. + +#include "shader_stage.h" + +#include +#include + +#include "libshaderc_util/file_finder.h" +#include "libshaderc_util/resources.h" + +#include "file.h" +#include "file_includer.h" + +using shaderc_util::string_piece; + +namespace { + +// Maps an identifier to a language. +struct LanguageMapping { + const char* id; + EShLanguage language; +}; + +} // anonymous namespace + +namespace glslc { + +EShLanguage GetShaderStageFromFileName(const string_piece& filename) { + // Add new stage types here. + static const LanguageMapping kStringToStage[] = { + {"vert", EShLangVertex}, {"frag", EShLangFragment}, + {"tesc", EShLangTessControl}, {"tese", EShLangTessEvaluation}, + {"geom", EShLangGeometry}, {"comp", EShLangCompute}}; + + const string_piece extension = glslc::GetFileExtension(filename); + if (extension.empty()) return EShLangCount; + + for (const auto& entry : kStringToStage) { + if (extension == entry.id) return entry.language; + } + return EShLangCount; +} + +EShLanguage MapStageNameToLanguage(const string_piece& stage_name) { + const LanguageMapping string_to_stage[] = { + {"vertex", EShLangVertex}, {"fragment", EShLangFragment}, + {"tesscontrol", EShLangTessControl}, {"tesseval", EShLangTessEvaluation}, + {"geometry", EShLangGeometry}, {"compute", EShLangCompute}}; + + for (const auto& entry : string_to_stage) { + if (stage_name == entry.id) return entry.language; + } + return EShLangCount; +} + +EShLanguage GetShaderStageFromCmdLine(const string_piece& f_shader_stage) { + size_t equal_pos = f_shader_stage.find_first_of("="); + if (equal_pos == std::string::npos) return EShLangCount; + return MapStageNameToLanguage(f_shader_stage.substr(equal_pos + 1)); +} + +} // namespace glslc diff --git a/glslc/src/shader_stage.h b/glslc/src/shader_stage.h new file mode 100644 index 000000000..850621383 --- /dev/null +++ b/glslc/src/shader_stage.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef GLSLC_SHADER_STAGE_H_ +#define GLSLC_SHADER_STAGE_H_ + +#include +#include +#include + +#include "glslang/Public/ShaderLang.h" + +#include "libshaderc_util/string_piece.h" + +namespace glslc { + +// Returns a glslang EShLanguage from a given command line -fshader-stage= +// argument option or EShLangCount if the shader stage could not be determined. +EShLanguage GetShaderStageFromCmdLine( + const shaderc_util::string_piece& f_shader_stage); + +// Returns a glslang EShLanguage from a given filename or EShLangCount +// if the shader stage could not be determined. +EShLanguage GetShaderStageFromFileName( + const shaderc_util::string_piece& filename); + +// Given a string representing a stage, returns the glslang EShLanguage for it. +// If the stage string is not recognized, returns EShLangCount. +EShLanguage MapStageNameToLanguage( + const shaderc_util::string_piece& stage_name); + +} // namespace glslc + +#endif // GLSLC_SHADER_STAGE_H_ diff --git a/glslc/src/version_profile.cc b/glslc/src/version_profile.cc new file mode 100644 index 000000000..45096973a --- /dev/null +++ b/glslc/src/version_profile.cc @@ -0,0 +1,58 @@ +// Copyright 2015 The Shaderc 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. + +#include "version_profile.h" + +#include + +namespace { + +const int kVersionNumberLength = 3; +const int kMaxProfileLength = 13; // strlen(compatibility) +const int kMaxVersionProfileLength = kVersionNumberLength + kMaxProfileLength; +const int kMinVersionProfileLength = kVersionNumberLength; + +} // anonymous namespace + +namespace glslc { + +bool GetVersionProfileFromCmdLine(const std::string& version_profile, + int* version, EProfile* profile) { + if (version_profile.size() < kMinVersionProfileLength || + version_profile.size() > kMaxVersionProfileLength || + !::isdigit(version_profile.front())) + return false; + + size_t split_point; + int version_number = std::stoi(version_profile, &split_point); + if (!IsKnownVersion(version_number)) return false; + *version = version_number; + + const std::string profile_string = version_profile.substr(split_point); + if (profile_string.empty()) { + *profile = ENoProfile; + } else if (profile_string == "core") { + *profile = ECoreProfile; + } else if (profile_string == "es") { + *profile = EEsProfile; + } else if (profile_string == "compatibility") { + *profile = ECompatibilityProfile; + } else { + return false; + } + + return true; +} + +} // namespace glslc diff --git a/glslc/src/version_profile.h b/glslc/src/version_profile.h new file mode 100644 index 000000000..64947e48a --- /dev/null +++ b/glslc/src/version_profile.h @@ -0,0 +1,40 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef GLSLC_VERSION_PROFILE_H_ +#define GLSLC_VERSION_PROFILE_H_ + +#include + +#include "glslang/MachineIndependent/Versions.h" + +namespace glslc { + +// Returns true if the given version is an accepted GLSL (ES) version. +inline bool IsKnownVersion(int version) { + return version == 100 || version == 110 || version == 120 || version == 130 || + version == 140 || version == 150 || version == 300 || version == 330 || + version == 310 || version == 400 || version == 410 || version == 420 || + version == 430 || version == 440 || version == 450; +} + +// Given a string version_profile containing both version and profile, decodes +// it and puts the decoded version in version, decoded profile in profile. +// Returns true if decoding is successful and version and profile are accepted. +bool GetVersionProfileFromCmdLine(const std::string& version_profile, + int* version, EProfile* profile); + +} // namespace glslc + +#endif // GLSLC_VERSION_PROFILE_H_ diff --git a/glslc/test/CMakeLists.txt b/glslc/test/CMakeLists.txt new file mode 100644 index 000000000..909eb6cd9 --- /dev/null +++ b/glslc/test/CMakeLists.txt @@ -0,0 +1,6 @@ +add_nosetests(glslc_test_framework) + +add_test(NAME glslc_tests + COMMAND ${PYTHON_EXE} ${CMAKE_CURRENT_SOURCE_DIR}/glslc_test_framework.py + $ --test-dir ${CMAKE_CURRENT_SOURCE_DIR}) + diff --git a/glslc/test/environment.py b/glslc/test/environment.py new file mode 100644 index 000000000..27f24cf47 --- /dev/null +++ b/glslc/test/environment.py @@ -0,0 +1,72 @@ +# Copyright 2015 The Shaderc 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. + +"""Classes for conveniently specifying a test environment. + +These classes have write() methods that create objects in a test's environment. +For instance, File creates a file, and Directory creates a directory with some +files or subdirectories in it. + +Example: + test.environment = Directory('.', [ + File('a.vert', 'void main(){}'), + Directory('subdir', [ + File('b', 'b content'), + File('c', 'c content') + ]) + ]) + +In general, these classes don't clean up the disk content they create. They +were written in a test framework that handles clean-up at a higher level. + +""" + +import os + +class Directory: + """Specifies a directory or a subdirectory.""" + def __init__(self, name, content): + """content is a list of File or Directory objects.""" + self.name = name + self.content = content + + @staticmethod + def create(path): + """Creates a directory path if it doesn't already exist.""" + try: + os.makedirs(path) + except OSError: # Handles both pre-existence and a racing creation. + if not os.path.isdir(path): + raise + + def write(self, parent): + """Creates a self.name directory within parent (which is a string path) and + recursively writes the content in it. + + """ + full_path = os.path.join(parent, self.name) + Directory.create(full_path) + for c in self.content: + c.write(full_path) + +class File: + """Specifies a file by name and content.""" + def __init__(self, name, content): + self.name = name + self.content = content + + def write(self, directory): + """Writes content to directory/name.""" + with open(os.path.join(directory, self.name), 'w') as f: + f.write(self.content) diff --git a/glslc/test/expect.py b/glslc/test/expect.py new file mode 100644 index 000000000..422199bc2 --- /dev/null +++ b/glslc/test/expect.py @@ -0,0 +1,364 @@ +# Copyright 2015 The Shaderc 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. + +"""A number of common glslc result checks coded in mixin classes. + +A test case can use these checks by declaring their enclosing mixin classes +as superclass and providing the expected_* variables required by the check_*() +methods in the mixin classes. +""" +import os.path +from glslc_test_framework import GlslCTest + +def convert_to_unix_line_endings(source): + """Converts all line endings in source to be unix line endings.""" + return source.replace('\r\n', '\n').replace('\r', '\n') + +def substitute_file_extension(filename, extension): + """Substitutes file extension, respecting known shader extensions. + + foo.vert -> foo.vert.[extension] [similarly for .frag, .comp, etc.] + foo.glsl -> foo.[extension] + foo.unknown -> foo.[extension] + foo -> foo.[extension] + """ + if filename[-5:] not in ['.vert', '.frag', '.tesc', '.tese', + '.geom', '.comp']: + return filename.rsplit('.', 1)[0] + '.' + extension + else: + return filename + '.' + extension + + +def get_object_filename(source_filename): + """Gets the object filename for the given source file.""" + return substitute_file_extension(source_filename, 'spv') + + +def get_assembly_filename(source_filename): + """Gets the assembly filename for the given source file.""" + return substitute_file_extension(source_filename, 's') + + +def verify_file_non_empty(filename): + """Checks that a given file exists and is not empty.""" + if not os.path.isfile(filename): + return False, 'Cannot find file: ' + filename + if not os.path.getsize(filename): + return False, 'Empty file: ' + filename + return True, '' + + +class ReturnCodeIsZero(GlslCTest): + """Mixin class for checking that the return code is zero.""" + + def check_return_code_is_zero(self, status): + if status.returncode: + return False, 'Non-zero return code: {ret}\n'.format( + ret=status.returncode) + return True, '' + + +class NoOutputOnStdout(GlslCTest): + """Mixin class for checking that there is no output on stdout.""" + + def check_no_output_on_stdout(self, status): + if status.stdout: + return False, 'Non empty stdout: {out}\n'.format(out=status.stdout) + return True, '' + + +class NoOutputOnStderr(GlslCTest): + """Mixin class for checking that there is no output on stderr.""" + + def check_no_output_on_stderr(self, status): + if status.stderr: + return False, 'Non empty stderr: {err}\n'.format(err=status.stderr) + return True, '' + + +class SuccessfulReturn(ReturnCodeIsZero, NoOutputOnStdout, NoOutputOnStderr): + """Mixin class for checking that return code is zero and no output on + stdout and stderr.""" + pass + + +class CorrectObjectFilePreamble(GlslCTest): + """Provides methods for verifying preamble for a SPV object file.""" + + def verify_object_file_preamble(self, filename): + """Checks that the given SPIR-V binary file has correct preamble.""" + + def read_word(binary, index, little_endian): + """Reads the index-th word from the given binary file.""" + word = binary[index * 4:(index + 1) * 4] + if little_endian: + word = reversed(word) + return reduce(lambda w, b: (w << 8) | ord(b), word, 0) + + def check_endianness(binary): + """Checks the endianness of the given SPIR-V binary file. + + Returns: + True if it's little endian, False if it's big endian. + None if magic number is wrong. + """ + first_word = read_word(binary, 0, True) + if first_word == 0x07230203: + return True + first_word = read_word(binary, 0, False) + if first_word == 0x07230203: + return False + return None + + success, message = verify_file_non_empty(filename) + if not success: + return False, message + + with open(filename, 'rb') as object_file: + object_file.seek(0, os.SEEK_END) + num_bytes = object_file.tell() + if num_bytes % 4 != 0: + return False, ('Incorrect SPV binary: size should be a multiple' + ' of words') + if num_bytes < 20: + return False, 'Incorrect SPV binary: size less than 5 words' + + object_file.seek(0) + preamble = bytes(object_file.read(20)) + + little_endian = check_endianness(preamble) + # SPIR-V module magic number + if little_endian is None: + return False, 'Incorrect SPV binary: wrong magic number' + + # SPIR-V version number + if read_word(preamble, 1, little_endian) != 99: + return False, 'Incorrect SPV binary: wrong version number' + # glslang SPIR-V magic number + if read_word(preamble, 2, little_endian) != 0x051a00bb: + return False, ('Incorrect SPV binary: wrong generator magic ' + 'number') + # reserved for instruction schema + if read_word(preamble, 4, little_endian) != 0: + return False, 'Incorrect SPV binary: the 5th byte should be 0' + + return True, '' + + +class CorrectAssemblyFilePreamble(GlslCTest): + """Provides methods for verifying preamble for a SPV assembly file.""" + + def verify_assembly_file_preamble(self, filename): + success, message = verify_file_non_empty(filename) + if not success: + return False, message + + with open(filename) as assembly_file: + first_line = assembly_file.readline() + second_line = assembly_file.readline() + + if (first_line != '// Module Version 99\n' or + second_line != '// Generated by (magic number): 51a00bb\n'): + return False, 'Incorrect SPV assembly' + + return True, '' + + +class ValidObjectFile(SuccessfulReturn, CorrectObjectFilePreamble): + """Mixin class for checking that every input file generates a valid object + file following the object file naming rule, and there is no output on + stdout/stderr.""" + + def check_object_file_preamble(self, status): + for input_filename in status.input_filenames: + object_filename = get_object_filename(input_filename) + success, message = self.verify_object_file_preamble( + os.path.join(status.directory, object_filename)) + if not success: + return False, message + return True, '' + +class ValidFileContents(GlslCTest): + """Mixin class to test that a specific file contains specific text + To mix in this class, subclasses need to provide expected_file_contents as + the contents of the file and target_filename to determine the location.""" + + def check_file(self, status): + target_filename = os.path.join(status.directory, self.target_filename) + if not os.path.isfile(target_filename): + return False, 'Cannot find file: ' + target_filename + with open(target_filename, 'r') as target_file: + file_contents = target_file.read() + if file_contents == self.expected_file_contents: + return True, '' + return False, ('Incorrect file output: \n{act}\nExpected:\n{exp}' + ''.format(act=file_contents, + exp=self.expected_file_contents)) + return False, ('Could not open target file ' + target_filename + + ' for reading') + + +class ValidAssemblyFile(SuccessfulReturn, CorrectAssemblyFilePreamble): + """Mixin class for checking that every input file generates a valid assembly + file following the assembly file naming rule, and there is no output on + stdout/stderr.""" + + def check_assembly_file_preamble(self, status): + for input_filename in status.input_filenames: + assembly_filename = get_assembly_filename(input_filename) + success, message = self.verify_assembly_file_preamble( + os.path.join(status.directory, assembly_filename)) + if not success: + return False, message + return True, '' + + +class ErrorMessage(GlslCTest): + """Mixin class for tests that fail with a specific error message. + + To mix in this class, subclasses need to provide expected_error as the + expected error message. + """ + + def check_has_error_message(self, status): + if not status.returncode: + return False, ('Expected error message, but returned success from ' + 'glslc') + if not status.stderr: + return False, 'Expected error message, but no output on stderr' + if self.expected_error != convert_to_unix_line_endings(status.stderr): + return False, ('Incorrect stderr output:\n{act}\n' + 'Expected:\n{exp}'.format( + act=status.stderr, exp=self.expected_error)) + return True, '' + + +class WarningMessage(GlslCTest): + """Mixin class for tests that succeed but have a specific warning message. + + To mix in this class, subclasses need to provide expected_warning as the + expected warning message. + """ + + def check_has_warning_message(self, status): + if status.returncode: + return False, ('Expected warning message, but returned failure from' + ' glslc') + if not status.stderr: + return False, 'Expected warning message, but no output on stderr' + if self.expected_warning != convert_to_unix_line_endings(status.stderr): + return False, ('Incorrect stderr output:\n{act}\n' + 'Expected:\n{exp}'.format( + act=status.stderr, exp=self.expected_warning)) + return True, '' + + +class ValidObjectFileWithWarning( + NoOutputOnStdout, CorrectObjectFilePreamble, WarningMessage): + """Mixin class for checking that every input file generates a valid object + file following the object file naming rule, with a specific warning message. + """ + + def check_object_file_preamble(self, status): + for input_filename in status.input_filenames: + object_filename = get_object_filename(input_filename) + success, message = self.verify_object_file_preamble( + os.path.join(status.directory, object_filename)) + if not success: + return False, message + return True, '' + + +class ValidAssemblyFileWithWarning( + NoOutputOnStdout, CorrectAssemblyFilePreamble, WarningMessage): + """Mixin class for checking that every input file generates a valid assembly + file following the assembly file naming rule, with a specific warning + message.""" + + def check_assembly_file_preamble(self, status): + for input_filename in status.input_filenames: + assembly_filename = get_assembly_filename(input_filename) + success, message = self.verify_assembly_file_preamble( + os.path.join(status.directory, assembly_filename)) + if not success: + return False, message + return True, '' + + +class StdoutMatch(GlslCTest): + """Mixin class for tests that can expect output on stdout. + + To mix in this class, subclasses need to provide expected_stdout as the + expected stdout output. + + For expected_stdout, if it's True, then they expect something on stdout, + but will not check what it is. If it's a string, expect an exact match. + """ + + def check_stdout_match(self, status): + # "True" in this case means we expect something on stdout, but we do not + # care what it is, we want to distinguish this from "blah" which means we + # expect exactly the string "blah". + if self.expected_stdout is True: + if not status.stdout: + return False, 'Expected something on stdout' + else: + if self.expected_stdout != convert_to_unix_line_endings(status.stdout): + return False, ('Incorrect stdout output:\n{ac}\n' + 'Expected:\n{ex}'.format( + ac=status.stdout, ex=self.expected_stdout)) + return True, '' + + +class StderrMatch(GlslCTest): + """Mixin class for tests that can expect output on stderr. + + To mix in this class, subclasses need to provide expected_stderr as the + expected stderr output. + + For expected_stderr, if it's True, then they expect something on stderr, + but will not check what it is. If it's a string, expect an exact match. + """ + + def check_stderr_match(self, status): + # "True" in this case means we expect something on stderr, but we do not + # care what it is, we want to distinguish this from "blah" which means we + # expect exactly the string "blah". + if self.expected_stderr is True: + if not status.stderr: + return False, 'Expected something on stderr' + else: + if self.expected_stderr != convert_to_unix_line_endings(status.stderr): + return False, ('Incorrect stderr output:\n{ac}\n' + 'Expected:\n{ex}'.format( + ac=status.stderr, ex=self.expected_stderr)) + return True, '' + + +class StdoutNoWiderThan80Columns(GlslCTest): + """Mixin class for tests that require stdout to 80 characters or narrower. + + To mix in this class, subclasses need to provide expected_stdout as the + expected stdout output. + """ + + def check_stdout_not_too_wide(self, status): + if not status.stdout: + return True, '' + else: + for line in status.stdout.splitlines(): + if len(line) > 80: + return False, ('Stdout line longer than 80 columns: %s' + % line) + return True, '' diff --git a/glslc/test/expect_nosetest.py b/glslc/test/expect_nosetest.py new file mode 100644 index 000000000..aa7f8bfd1 --- /dev/null +++ b/glslc/test/expect_nosetest.py @@ -0,0 +1,33 @@ +# Copyright 2015 The Shaderc 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. + +"""Tests for the expect module.""" + +from expect import get_object_filename +from nose.tools import assert_equal + +def nosetest_get_object_name(): + """Tests get_object_filename().""" + source_and_object_names = [ + ('a.vert', 'a.vert.spv'), ('b.frag', 'b.frag.spv'), + ('c.tesc', 'c.tesc.spv'), ('d.tese', 'd.tese.spv'), + ('e.geom', 'e.geom.spv'), ('f.comp', 'f.comp.spv'), + ('file', 'file.spv'), ('file.', 'file.spv'), + ('file.uk', 'file.spv'), ('file.vert.', 'file.vert.spv'), + ('file.vert.bla', 'file.vert.spv')] + actual_object_names = [ + get_object_filename(f[0]) for f in source_and_object_names] + expected_object_names = [f[1] for f in source_and_object_names] + + assert_equal(actual_object_names, expected_object_names) diff --git a/glslc/test/file_extensions.py b/glslc/test/file_extensions.py new file mode 100644 index 000000000..692dd1807 --- /dev/null +++ b/glslc/test/file_extensions.py @@ -0,0 +1,86 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import GlslCTest, inside_glslc_testsuite +from placeholder import FileShader + + +def empty_es_310_shader(): + return '#version 310 es\n void main() {}\n' + + +@inside_glslc_testsuite('FileExtension') +class VerifyVertExtension(expect.ValidObjectFile): + """Tests glslc accepts vertex shader extension (.vert).""" + + shader = FileShader(empty_es_310_shader(), '.vert') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('FileExtension') +class VerifyFragExtension(expect.ValidObjectFile): + """Tests glslc accepts fragment shader extension (.frag).""" + + shader = FileShader(empty_es_310_shader(), '.frag') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('FileExtension') +class VerifyTescExtension(expect.ValidObjectFile): + """Tests glslc accepts tessellation control shader extension (.tesc).""" + + shader = FileShader( + '#version 440 core\n layout(vertices = 3) out;\n void main() {}', + '.tesc') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('FileExtension') +class VerifyTeseExtension(expect.ValidObjectFile): + """Tests glslc accepts tessellation evaluation shader extension (.tese).""" + + shader = FileShader( + '#version 440 core\n layout(triangles) in;\n void main() {}', '.tese') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('FileExtension') +class VerifyGeomExtension(expect.ValidObjectFile): + """Tests glslc accepts geomtry shader extension (.geom).""" + + shader = FileShader( + '#version 150 core\n layout (triangles) in;\n' + 'layout (line_strip, max_vertices = 4) out;\n void main() {}', + '.geom') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('FileExtension') +class VerifyCompExtension(expect.ValidObjectFile): + """Tests glslc accepts compute shader extension (.comp).""" + + shader = FileShader(empty_es_310_shader(), '.comp') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('FileExtension') +class InvalidExtension(expect.ErrorMessage): + """Tests the error message if a file extension cannot be determined.""" + + shader = FileShader('', '.fraga') + glslc_args = ['-c', shader] + expected_error = [ + "glslc: error: '", shader, + "': file not recognized: File format not recognized\n"] diff --git a/glslc/test/glslc_test_framework.py b/glslc/test/glslc_test_framework.py new file mode 100755 index 000000000..07a6bc398 --- /dev/null +++ b/glslc/test/glslc_test_framework.py @@ -0,0 +1,333 @@ +#!/usr/bin/env python +# Copyright 2015 The Shaderc 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. + +"""Manages and runs tests from the current working directory. + +This will traverse the current working directory and look for python files that +contain subclasses of GlslCTest. + +If a class has an @inside_glslc_testsuite decorator, an instance of that +class will be created and serve as a test case in that testsuite. The test +case is then run by the following steps: + + 1. A temporary directory will be created. + 2. The glslc_args member variable will be inspected and all placeholders in it + will be expanded by calling instantiate_for_glslc_args() on placeholders. + The transformed list elements are then supplied as glslc arguments. + 3. If the environment member variable exists, its write() method will be + invoked. + 4. All expected_* member varibles will be inspected and all placeholders in + them will be expanded by calling instantiate_for_expectation() on those + placeholders. After placeholder expansion, if the expected_* variable is + a list, it's element will be joined together with '' to form a single + string. These expected_* variables are to be used by the check_*() methods. + 5. glslc will be run with the arguments supplied in glslc_args. + 6. All check_*() member methods will be called by supplying a TestStatus as + argument. Each check_*() method is expected to return a (Success, Message) + pair where Success is a boolean indicating success and Message is an error + message. + 7. If any check_*() method fails, the error message is outputted and the + current test case fails. + +If --leave-output was not specified, all temporary files and directories will +be deleted. +""" + +import argparse +import fnmatch +import inspect +import os +import shutil +import subprocess +import sys +import tempfile +from collections import defaultdict +from placeholder import PlaceHolder + + +EXPECTED_BEHAVIOR_PREFIX = 'expected_' +VALIDATE_METHOD_PREFIX = 'check_' + + +def get_all_variables(instance): + """Returns the names of all the variables in instance.""" + return [v for v in dir(instance) if not callable(getattr(instance, v))] + + +def get_all_methods(instance): + """Returns the names of all methods in instance.""" + return [m for m in dir(instance) if callable(getattr(instance, m))] + + +def get_all_superclasses(cls): + """Returns all superclasses of a given class. + + Returns: + A list of superclasses of the given class. The order guarantees that + * A Base class precedes its derived classes, e.g., for "class B(A)", it + will be [..., A, B, ...]. + * When there are multiple base classes, base classes declared first + precede those declared later, e.g., for "class C(A, B), it will be + [..., A, B, C, ...] + """ + classes = [] + for superclass in cls.__bases__: + for c in get_all_superclasses(superclass): + if c not in classes: + classes.append(c) + for superclass in cls.__bases__: + if superclass not in classes: + classes.append(superclass) + return classes + + +def get_all_test_methods(test_class): + """Gets all validation methods. + + Returns: + A list of validation methods. The order guarantees that + * A method defined in superclass precedes one defined in subclass, + e.g., for "class A(B)", methods defined in B precedes those defined + in A. + * If a subclass has more than one superclass, e.g., "class C(A, B)", + then methods defined in A precedes those defined in B. + """ + classes = get_all_superclasses(test_class) + classes.append(test_class) + all_tests = [m for c in classes + for m in get_all_methods(c) + if m.startswith(VALIDATE_METHOD_PREFIX)] + unique_tests = [] + for t in all_tests: + if t not in unique_tests: + unique_tests.append(t) + return unique_tests + + +class GlslCTest: + """Base class for glslc test cases. + + Subclasses define test cases' facts (shader source code, glslc command, + result validation), which will be used by the TestCase class for running + tests. Subclasses should define glslc_args (specifying glslc command + arguments), and at least one check_*() method (for result validation) for + a full-fledged test case. All check_*() methods should take a TestStatus + parameter and return a (Success, Message) pair, in which Success is a + boolean indicating success and Message is an error message. The test passes + iff all check_*() methods returns true. + + Often, a test case class will delegate the check_* behaviors by inheriting + from other classes. + """ + + def name(self): + return self.__class__.__name__ + + +class TestStatus: + """A struct for holding run status of a test case.""" + + def __init__(self, returncode, stdout, stderr, directory, input_filenames): + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + # temporary directory where the test runs + self.directory = directory + # the names of input shader files (without path) + self.input_filenames = input_filenames + + +class GlslCTestException(Exception): + """GlslCTest exception class.""" + pass + + +def inside_glslc_testsuite(testsuite_name): + """Decorator for subclasses of GlslCTest. + + This decorator checks that a class meets the requirements (see below) + for a test case class, and then puts the class in a certain testsuite. + * The class needs to be a subclass of GlslCTest. + * The class needs to have glslc_args defined as a list. + * The class needs to define at least one check_*() methods. + * All expected_* variables required by check_*() methods can only be + of bool, str, or list type. + * Python runtime will throw an exception if the expected_* member + attributes required by check_*() methods are missing. + """ + def actual_decorator(cls): + if not inspect.isclass(cls): + raise GlslCTestException('Test case should be a class') + if not issubclass(cls, GlslCTest): + raise GlslCTestException( + 'All test cases should be subclasses of GlslCTest') + if 'glslc_args' not in get_all_variables(cls): + raise GlslCTestException('No glslc_args found in the test case') + if not isinstance(cls.glslc_args, list): + raise GlslCTestException('glslc_args needs to be a list') + if not any([ + m.startswith(VALIDATE_METHOD_PREFIX) + for m in get_all_methods(cls)]): + raise GlslCTestException( + 'No check_*() methods found in the test case') + if not all([ + isinstance(v, (bool, str, list)) + for v in get_all_variables(cls)]): + raise GlslCTestException( + 'expected_* variables are only allowed to be bool, str, or ' + 'list type.') + cls.parent_testsuite = testsuite_name + return cls + return actual_decorator + + +class TestManager: + """Manages and runs a set of tests.""" + + def __init__(self, executable_path): + self.executable_path = executable_path + self.num_successes = 0 + self.num_failures = 0 + self.num_tests = 0 + self.leave_output = False + self.tests = defaultdict(list) + + def notify_result(self, test_case, success, message): + """Call this to notify the manager of the results of a test run.""" + self.num_successes += 1 if success else 0 + self.num_failures += 0 if success else 1 + counter_string = str( + self.num_successes + self.num_failures) + '/' + str(self.num_tests) + print ('%-10s %-40s ' % (counter_string, test_case.test.name()) + + ('Passed' if success else '-Failed-')) + if not success: + print ' '.join(test_case.command) + print message + + def add_test(self, testsuite, test): + """Add this to the current list of test cases.""" + self.tests[testsuite].append(TestCase(test, self)) + self.num_tests += 1 + + def run_tests(self): + for suite in self.tests: + print('Glslc test suite: "{suite}"'.format(suite=suite)) + for x in self.tests[suite]: + x.runTest() + + +class TestCase: + """A single test case that runs in its own directory.""" + + def __init__(self, test, test_manager): + self.test = test + self.test_manager = test_manager + self.file_shaders = [] # filenames of shader files + self.stdin_shader = None # text to be passed to glslc as stdin + + def setUp(self): + """Creates environment and instantiates placeholders for the test case.""" + + self.directory = tempfile.mkdtemp(dir=os.getcwd()) + glslc_args = self.test.glslc_args + # Instantiate placeholders in glslc_args + self.test.glslc_args = [ + arg.instantiate_for_glslc_args(self) + if isinstance(arg, PlaceHolder) else arg + for arg in self.test.glslc_args] + # Get all shader files' names + self.file_shaders = [ + arg.filename for arg in glslc_args if isinstance(arg, PlaceHolder)] + + if 'environment' in get_all_variables(self.test): + self.test.environment.write(self.directory) + + expectations = [v for v in get_all_variables(self.test) + if v.startswith(EXPECTED_BEHAVIOR_PREFIX)] + # Instantiate placeholders in expectations + for expectation_name in expectations: + expectation = getattr(self.test, expectation_name) + if isinstance(expectation, list): + expanded_expections = [ + element.instantiate_for_expectation(self) + if isinstance(element, PlaceHolder) else element + for element in expectation] + setattr( + self.test, expectation_name, + ''.join(expanded_expections)) + + def tearDown(self): + """Removes the directory if we were not instructed to do otherwise.""" + if not self.test_manager.leave_output: + shutil.rmtree(self.directory) + + def runTest(self): + """Sets up and runs a test, reports any failures and then cleans up.""" + self.setUp() + success = False + message = '' + try: + self.command = [self.test_manager.executable_path] + self.command.extend(self.test.glslc_args) + + process = subprocess.Popen( + args=self.command, stdin=subprocess.PIPE, + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + cwd=self.directory) + output = process.communicate(self.stdin_shader) + test_status = TestStatus( + process.returncode, output[0], output[1], + self.directory, self.file_shaders) + for test_method in get_all_test_methods(self.test.__class__): + success, message = getattr(self.test, test_method)(test_status) + if not success: + break + except Exception as e: + success = False + message = str(e) + self.test_manager.notify_result(self, success, message) + self.tearDown() + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument('glslc', metavar='path/to/glslc', type=str, nargs=1, + help='Path to glslc') + parser.add_argument('--leave-output', action='store_const', const=1, + help='Do not clean up temporary directories') + parser.add_argument('--test-dir', nargs=1, + help='Directory to gather the tests from') + args = parser.parse_args() + default_path = sys.path + root_dir = os.getcwd() + if args.test_dir: + root_dir = args.test_dir[0] + manager = TestManager(args.glslc[0]) + if args.leave_output: + manager.leave_output = True + for root, _, filenames in os.walk(root_dir): + for filename in fnmatch.filter(filenames, '*.py'): + sys.path = default_path + sys.path.append(root) + mod = __import__(os.path.splitext(filename)[0]) + for _, obj, in inspect.getmembers(mod): + if inspect.isclass(obj) and hasattr(obj, 'parent_testsuite'): + manager.add_test(obj.parent_testsuite, obj()) + manager.run_tests() + if manager.num_failures > 0: + sys.exit(-1) + +if __name__ == '__main__': + main() diff --git a/glslc/test/glslc_test_framework_nosetest.py b/glslc/test/glslc_test_framework_nosetest.py new file mode 100644 index 000000000..b6bded50b --- /dev/null +++ b/glslc/test/glslc_test_framework_nosetest.py @@ -0,0 +1,104 @@ +# Copyright 2015 The Shaderc 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. + +from glslc_test_framework import get_all_test_methods, get_all_superclasses +from nose.tools import assert_equal, with_setup + + +# Classes to be used in testing get_all_{superclasses|test_methods}() +class Root: + def check_root(self): pass + +class A(Root): + def check_a(self): pass +class B(Root): + def check_b(self): pass +class C(Root): + def check_c(self): pass +class D(Root): + def check_d(self): pass +class E(Root): + def check_e(self): pass + +class H(B, C, D): + def check_h(self): pass +class I(E): + def check_i(self): pass + +class O(H, I): + def check_o(self): pass + +class U(A, O): + def check_u(self): pass + +class X(U, A): + def check_x(self): pass + +class R1: + def check_r1(self): pass +class R2: + def check_r2(self): pass + +class Multi(R1, R2): + def check_multi(self): pass + +def nosetest_get_all_superclasses(): + """Tests get_all_superclasses().""" + + assert_equal(get_all_superclasses(A), [Root]) + assert_equal(get_all_superclasses(B), [Root]) + assert_equal(get_all_superclasses(C), [Root]) + assert_equal(get_all_superclasses(D), [Root]) + assert_equal(get_all_superclasses(E), [Root]) + + assert_equal(get_all_superclasses(H), [Root, B, C, D]) + assert_equal(get_all_superclasses(I), [Root, E]) + + assert_equal(get_all_superclasses(O), [Root, B, C, D, E, H, I]) + + assert_equal(get_all_superclasses(U), [Root, B, C, D, E, H, I, A, O]) + assert_equal(get_all_superclasses(X), [Root, B, C, D, E, H, I, A, O, U]) + + assert_equal(get_all_superclasses(Multi), [R1, R2]) + + +def nosetest_get_all_methods(): + """Tests get_all_test_methods().""" + assert_equal(get_all_test_methods(A), ['check_root', 'check_a']) + assert_equal(get_all_test_methods(B), ['check_root', 'check_b']) + assert_equal(get_all_test_methods(C), ['check_root', 'check_c']) + assert_equal(get_all_test_methods(D), ['check_root', 'check_d']) + assert_equal(get_all_test_methods(E), ['check_root', 'check_e']) + + assert_equal( + get_all_test_methods(H), + ['check_root', 'check_b', 'check_c', 'check_d', 'check_h']) + assert_equal(get_all_test_methods(I), ['check_root', 'check_e', 'check_i']) + + assert_equal( + get_all_test_methods(O), + ['check_root', 'check_b', 'check_c', 'check_d', + 'check_e', 'check_h', 'check_i', 'check_o']) + + assert_equal( + get_all_test_methods(U), + ['check_root', 'check_b', 'check_c', 'check_d', 'check_e', + 'check_h', 'check_i', 'check_a', 'check_o', 'check_u']) + assert_equal( + get_all_test_methods(X), + ['check_root', 'check_b', 'check_c', 'check_d', 'check_e', + 'check_h', 'check_i', 'check_a', 'check_o', 'check_u', 'check_x']) + + assert_equal( + get_all_test_methods(Multi), ['check_r1', 'check_r2', 'check_multi']) diff --git a/glslc/test/include.py b/glslc/test/include.py new file mode 100644 index 000000000..229eb1e08 --- /dev/null +++ b/glslc/test/include.py @@ -0,0 +1,194 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from environment import File, Directory + + +@inside_glslc_testsuite('Include') +class VerifyIncludeOneSibling(expect.StdoutMatch): + """Tests #including a sibling file.""" + + environment = Directory('.', [ + File('a.vert', 'content a\n#include "b"\n'), + File('b', 'content b\n')]) + + glslc_args = ['-E', 'a.vert'] + + expected_stdout = 'content a\ncontent b\n' + +@inside_glslc_testsuite('Include') +class VerifyCompileIncludeOneSibling(expect.ValidObjectFile): + """Tests #including a sibling file via full compilation.""" + + environment = Directory('.', [ + File('a.vert', 'void foo(){}\n#include "b"\n'), + File('b', 'void main(){foo();}\n')]) + + glslc_args = ['a.vert'] + +@inside_glslc_testsuite('Include') +class VerifyIncludeWithoutNewline(expect.StdoutMatch): + """Tests a #include without a newline.""" + + environment = Directory('.', [ + File('a.vert', '#include "b"'), + File('b', 'content b\n')]) + + glslc_args = ['-E', 'a.vert'] + + expected_stdout = 'content b\n' + +@inside_glslc_testsuite('Include') +class VerifyCompileIncludeWithoutNewline(expect.ValidObjectFile): + """Tests a #include without a newline via full compilation.""" + + environment = Directory('.', [ + File('a.vert', + """void main + #include "b" + """), + File('b', + """#define PAR () + PAR{} + """)]) + + glslc_args = ['a.vert'] + +@inside_glslc_testsuite('Include') +class VerifyIncludeTwoSiblings(expect.StdoutMatch): + """Tests #including two sibling files.""" + + environment = Directory('.', [ + File('b.vert', '#include "a"\ncontent b\n#include "c"\n'), + File('a', 'content a\n'), + File('c', 'content c\n')]) + + glslc_args = ['-E', 'b.vert'] + + expected_stdout = 'content a\ncontent b\ncontent c\n' + +@inside_glslc_testsuite('Include') +class VerifyCompileIncludeTwoSiblings(expect.ValidObjectFile): + """Tests #including two sibling files via full compilation.""" + + environment = Directory('.', [ + File('b.vert', + """#include "a" + void bfun(){afun();} + #include "c" + """), + File('a', + """void afun(){} + #define BODY {} + """), + File('c', 'void main() BODY\n')]) + + glslc_args = ['b.vert'] + +@inside_glslc_testsuite('Include') +class VerifyNestedIncludeAmongSiblings(expect.StdoutMatch): + """Tests #include inside #included sibling files.""" + + environment = Directory('.', [ + File('a.vert', '#include "b"\ncontent a\n'), + File('b', 'content b\n#include "c"\n'), + File('c', 'content c\n')]) + + glslc_args = ['-E', 'a.vert'] + + # TODO(deki): there should be a newline after "content c". Fix it after we + # start generating #line in included files. This seems to only affect -E, + # though: the actual compilation works as if the newline is there. + expected_stdout = 'content b\ncontent c content a\n' + +@inside_glslc_testsuite('Include') +class VerifyCompileNestedIncludeAmongSiblings(expect.ValidObjectFile): + """Tests #include inside #included sibling files via full compilation.""" + + environment = Directory('.', [ + File('a.vert', + """#define BODY {} + #include "b" + void main(){cfun();} + """), + File('b', + """void bfun() BODY + #include "c" + """), + File('c', + """#define BF bfun() + void cfun(){BF;} + """)]) + + glslc_args = ['a.vert'] + +@inside_glslc_testsuite('Include') +class VerifyIncludeSubdir(expect.StdoutMatch): + """Tests #including a file from a subdirectory.""" + + environment = Directory('.', [ + File('a.vert', 'content a1\n#include "subdir/a"\ncontent a2\n'), + Directory('subdir', [File('a', 'content suba\n')])]) + + glslc_args = ['-E', 'a.vert'] + + expected_stdout = 'content a1\ncontent suba\ncontent a2\n' + +@inside_glslc_testsuite('Include') +class VerifyCompileIncludeSubdir(expect.ValidObjectFile): + """Tests #including a file from a subdirectory via full compilation.""" + + environment = Directory('.', [ + File('a.vert', + """#define BODY {} + #include "subdir/a" + void afun()BODY + """), + Directory('subdir', [File('a', 'void main() BODY\n')])]) + + glslc_args = ['a.vert'] + +@inside_glslc_testsuite('Include') +class VerifyIncludeDeepSubdir(expect.StdoutMatch): + """Tests #including a file from a subdirectory nested a few levels down.""" + + environment = Directory('.', [ + File('a.vert', + 'content a1\n#include "dir/subdir/subsubdir/a"\ncontent a2\n'), + Directory('dir', [ + Directory('subdir', [ + Directory('subsubdir', [File('a', 'content incl\n')])])])]) + + glslc_args = ['-E', 'a.vert'] + + expected_stdout = 'content a1\ncontent incl\ncontent a2\n' + +@inside_glslc_testsuite('Include') +class VerifyCompileIncludeDeepSubdir(expect.ValidObjectFile): + """Tests #including a file from a subdirectory nested a few levels down + via full compilation.""" + + environment = Directory('.', [ + File('a.vert', + """#define BODY {} + #include "dir/subdir/subsubdir/a" + void afun()BODY + """), + Directory('dir', [ + Directory('subdir', [ + Directory('subsubdir', [File('a', 'void main() BODY\n')])])])]) + + glslc_args = ['a.vert'] diff --git a/glslc/test/messages_tests.py b/glslc/test/messages_tests.py new file mode 100644 index 000000000..a21798002 --- /dev/null +++ b/glslc/test/messages_tests.py @@ -0,0 +1,322 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, StdinShader + + +@inside_glslc_testsuite('ErrorMessages') +class MultipleErrors(expect.ErrorMessage): + """Test Multiple error messages generated.""" + shader = FileShader('int main() {}', '.vert') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":1: error: 'int' : main function cannot return a value\n", + shader, ":1: error: '' : function does not return a value: main\n", + '2 errors generated.\n'] + + +@inside_glslc_testsuite('ErrorMessages') +class OneError(expect.ErrorMessage): + """Tests that only one error message is generated correctly.""" + + shader = FileShader( + """ + int a() { + } + void main() { + int x = a(); + } + """, '.vert') + glslc_args = ['-c', shader] + + expected_error = [ + shader, ":2: error: '' : function does not return a value: a\n", + '1 error generated.\n'] + + +@inside_glslc_testsuite('ErrorMessages') +class ManyLineError(expect.ErrorMessage): + """Tests that only one error message is generated correctly.""" + + shader = FileShader( + """ + + + + + + + + + + + int a() { + } + void main() { + int x = a(); + } + """, '.vert') + glslc_args = ['-c', shader] + + expected_error = [ + shader, ":12: error: '' : function does not return a value: a\n", + '1 error generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class GlobalWarning(expect.WarningMessage): + """Tests that a warning message without file/line number is emitted.""" + + shader = FileShader( + """ + #version 550 + void main() { + } + """, '.vert') + glslc_args = ['-c', shader] + + expected_warning = [ + shader, ': warning: version 550 is unknown.\n1 warning generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class SuppressedGlobalWarning(expect.SuccessfulReturn): + """Tests that warning messages without file/line numbers are suppressed + with -w.""" + + shader = FileShader( + """ + #version 550 + void main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-w'] + + +@inside_glslc_testsuite('ErrorMessages') +class GlobalWarningAsError(expect.ErrorMessage): + """Tests that with -Werror an error warning message without file/line + number is emitted instead of a warning.""" + + shader = FileShader( + """ + #version 550 + void main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-Werror'] + + expected_error= [ + shader, ': error: version 550 is unknown.\n1 error generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class WarningOnLine(expect.WarningMessage): + """Tests that a warning message with a file/line number is emitted.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + glslc_args = ['-c', shader] + + expected_warning = [ + shader, ':3: warning: attribute deprecated in version 130; ', + 'may be removed in future release\n1 warning generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class SuppressedWarningOnLine(expect.SuccessfulReturn): + """Tests that a warning message with a file/line number is suppressed in the + presence of -w.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-w'] + +@inside_glslc_testsuite('ErrorMessages') +class WarningOnLineAsError(expect.ErrorMessage): + """Tests that with -Werror an error message with a file/line + number is emitted instead of a warning.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-Werror'] + + expected_error = [ + shader, ':3: error: attribute deprecated in version 130; ', + 'may be removed in future release\n1 error generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class WarningAndError(expect.ErrorMessage): + """Tests that both warnings and errors are emitted together.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + int main() { + } + """, '.vert') + glslc_args = ['-c', shader] + + expected_error = [ + shader, ':3: warning: attribute deprecated in version 130; ', + 'may be removed in future release\n', + shader, ":4: error: 'int' : main function cannot return a value\n", + shader, ":4: error: '' : function does not return a value: main\n", + '1 warning and 2 errors generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class SuppressedWarningAndError(expect.ErrorMessage): + """Tests that only warnings are suppressed in the presence of -w.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + int main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-w'] + + expected_error = [ + shader, ":4: error: 'int' : main function cannot return a value\n", + shader, ":4: error: '' : function does not return a value: main\n", + '2 errors generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class WarningAsErrorAndError(expect.ErrorMessage): + """Tests that with -Werror an warnings and errors are emitted as errors.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + int main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-Werror'] + + expected_error = [ + shader, ':3: error: attribute deprecated in version 130; ', + 'may be removed in future release\n', + shader, ":4: error: 'int' : main function cannot return a value\n", + shader, ":4: error: '' : function does not return a value: main\n", + '3 errors generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class StdinErrorMessages(expect.StdoutMatch, expect.StderrMatch): + """Tests that error messages using input from stdin are correct.""" + + shader = StdinShader( + """ + int a() { + } + void main() { + int x = a(); + } + """) + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + expected_stdout = '' + expected_stderr = [ + ":2: error: '' : function does not return a value: a\n", + '1 error generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class WarningAsErrorMultipleFiles(expect.ErrorMessage): + """Tests that with -Werror multiple files emit errors instead of warnings. + """ + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + + shader2 = FileShader( + """ + #version 550 + void main() { + } + """, '.vert') + + glslc_args = ['-c', shader, '-Werror', shader2] + + expected_error = [ + shader, ':3: error: attribute deprecated in version 130; ', + 'may be removed in future release\n', + shader2, ': error: version 550 is unknown.\n', + '2 errors generated.\n'] + +@inside_glslc_testsuite('ErrorMessages') +class SuppressedWarningAsError(expect.SuccessfulReturn): + """Tests that nothing is returned in the presence of -w -Werror.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + glslc_args = ['-c', shader, '-w', '-Werror'] + +@inside_glslc_testsuite('ErrorMessages') +class MultipleSuppressed(expect.SuccessfulReturn): + """Tests that multiple -w arguments are supported.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + glslc_args = ['-w', '-c', shader, '-w', '-w', '-w'] + +@inside_glslc_testsuite('ErrorMessages') +class MultipleSuppressedFiles(expect.SuccessfulReturn): + """Tests that -w suppresses warnings from all files.""" + + shader = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + + shader2 = FileShader( + """ + #version 130 + attribute float x; + void main() { + } + """, '.vert') + glslc_args = ['-w', '-c', shader, shader2] diff --git a/glslc/test/option_dash_D.py b/glslc/test/option_dash_D.py new file mode 100644 index 000000000..8405dcb69 --- /dev/null +++ b/glslc/test/option_dash_D.py @@ -0,0 +1,363 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDNoArg(expect.ErrorMessage): + """Tests -D without macroname.""" + + glslc_args = ['-D'] + expected_error = [ + "glslc: error: argument to '-D' is missing\n", + 'glslc: error: no input files\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDXeqY(expect.ValidObjectFile): + """Tests -DX=Y.""" + + shader = FileShader('void main(){X=vec4(1.);}', '.vert') + glslc_args = ['-c', '-DX=gl_Position', shader] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDXeq(expect.ValidObjectFile): + """Tests -DX=.""" + + shader = FileShader('void main(){X}', '.vert') + glslc_args = ['-c', '-DX=', shader] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDX(expect.ValidObjectFile): + """Tests -DX.""" + + shader = FileShader('void main(){X}', '.vert') + glslc_args = ['-c', '-DX', shader] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDeq(expect.ErrorMessage): + """Tests -D=. + + This is actually allowed by clang, though the resulting #define + causes a preprocessing error. + """ + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-c', '-D=', shader] + expected_error = [ + shader, ":1: error: '#define' : must be followed by macro name\n", + '1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestMultipleDashCapD(expect.ValidObjectFile): + """Tests multiple -D occurrences.""" + + shader = FileShader('void main(){X Y a=Z;}', '.vert') + glslc_args = ['-c', '-DX', '-DY=int', '-DZ=(1+2)', shader] + + +@inside_glslc_testsuite('OptionCapD') +class TestMultipleDashCapDOfSameName(expect.ValidObjectFile): + """Tests multiple -D occurrences with same macro name.""" + + shader = FileShader('void main(){X Y a=Z;}', '.vert') + glslc_args = ['-c', '-DX=main', '-DY=int', '-DZ=(1+2)', '-DX', shader] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDGL_(expect.ErrorMessage): + """Tests that we cannot -D macros beginning with GL_.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-DGL_ES=1', shader] + expected_error = [ + "glslc: error: names beginning with 'GL_' cannot be " + 'defined: -DGL_ES=1\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDReservedMacro(expect.WarningMessage): + """Tests that we cannot -D GLSL's predefined macros.""" + + shader = FileShader('void main(){}', '.vert') + # Consecutive underscores are banned anywhere in the name. + glslc_args = [ + '-D__LINE__=1', '-Dmid__dle', '-Dend__', '-D_single_is_valid_', shader] + + w = 'glslc: warning: names containing consecutive underscores are reserved: ' + expected_warning = [w, '-D__LINE__=1\n', w, '-Dmid__dle\n', w, '-Dend__\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithVersion(expect.ErrorMessage): + """Tests -D works well when #version is present.""" + + shader = FileShader( + """#version 310 es + void main(){X} + void foo(){Y}""", '.vert') + glslc_args = ['-DX=', '-DY=return 3;', shader] + + expected_error = [ + shader, ":3: error: 'return' : void function cannot return a value\n", + '1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithCommentBeforeVersion(expect.ErrorMessage): + """Tests -D works well with #version preceded by comments.""" + + shader = FileShader( + """// comment 1 + /* + * comment 2 + */ + #version 450 core + void main(){X} + void foo(){Y}""", '.vert') + glslc_args = ['-DX=', '-DY=return 3;', shader] + + expected_error = [ + shader, ":7: error: 'return' : void function cannot return a value\n", + '1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithCommentAfterVersion(expect.ErrorMessage): + """Tests -D works well with #version followed by comments.""" + + shader = FileShader( + """ + + #version 150 core /* + comment + */ + void main(){X} + void foo(){Y}""", '.vert') + glslc_args = ['-DX=', '-DY=return 3;', shader] + + expected_error = [ + shader, ":7: error: 'return' : void function cannot return a value\n", + '1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashStd(expect.ErrorMessage): + """Tests -D works well with -std.""" + + shader = FileShader('void main(){X}\nvoid foo(){Y}', '.vert') + glslc_args = ['-DX=', '-DY=return 3;', '-std=310es', shader] + + expected_error = [ + shader, ":2: error: 'return' : void function cannot return a value\n", + '1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashStdAndVersion(expect.ErrorMessage): + """Tests -D works well with both -std and #version.""" + + shader = FileShader( + """#version 310 es + void main(){X} + void foo(){Y}""", '.vert') + glslc_args = ['-DX=', '-DY=return 3;', '-std=450compatibility', shader] + + expected_error = [ + shader, ': warning: (version, profile) forced to be (450, ', + 'compatibility), while in source code it is (310, es)\n', + shader, ":3: error: 'return' : void function cannot return a value\n", + '1 warning and 1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashStdAndVersionAndComments(expect.ErrorMessage): + """Tests -D works well with -std, #version, and comments around it.""" + + shader = FileShader( + """// comment before + + #version 310 es /* comment after + */ + + void main(){X} + void foo(){Y}""", '.vert') + glslc_args = ['-DX=', '-DY=return 3;', '-std=450core', shader] + + expected_error = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (310, es)\n', + shader, ":7: error: 'return' : void function cannot return a value\n", + '1 warning and 1 error generated.\n'] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashE(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E outputs expanded -D macros.""" + + shader = FileShader( + """ + void main(){Y} +""", '.vert') + glslc_args = ['-DY=return 3;', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + void main(){ return 3;} +"""] + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashEIfDef(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E processes -DX #ifdefs correctly.""" + + shader = FileShader( + """ + #ifdef X + void f() { } + #else + void f() { int x; } + #endif + void main(){ return 3;} +""", '.vert') + glslc_args = ['-DX', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + + void f(){ } + + + + void main(){ return 3;} +"""] + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashEIfNDef(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E processes -DX #ifndefs correctly.""" + + shader = FileShader( + """ + #ifndef X + void f() { } + #else + void f() { int x; } + #endif + void main(){ return 3;} +""", '.vert') + glslc_args = ['-DX', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + + + + void f(){ int x;} + + void main(){ return 3;} +"""] + + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashEEqIfDef(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E processes -DX= #ifdefs correctly.""" + + shader = FileShader( + """ + #ifdef X + void f() { } + #else + void f() { int x; } + #endif + void main(){ return 3;} +""", '.vert') + glslc_args = ['-DX=', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + + void f(){ } + + + + void main(){ return 3;} +"""] + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashEEqIfNDef(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E processes -DX= #ifndefs correctly.""" + + shader = FileShader( + """ + #ifndef X + void f() { } + #else + void f() { int x; } + #endif + void main(){ return 3;} +""", '.vert') + glslc_args = ['-DX=', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + + + + void f(){ int x;} + + void main(){ return 3;} +"""] + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashEFunctionMacro(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E processes -D function macros correctly.""" + + shader = FileShader( + """ + void main(){ return FOO(3);} +""", '.vert') + glslc_args = ['-DFOO(x)=(2*x+1)*x*x', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + void main(){ return(2 * 3 + 1)* 3 * 3;} +"""] + +@inside_glslc_testsuite('OptionCapD') +class TestDashCapDWithDashENestedMacro(expect.ReturnCodeIsZero, + expect.StdoutMatch): + """Tests -E processes referencing -D options correctly.""" + + shader = FileShader( + """ + void main(){ return X;} +""", '.vert') + glslc_args = ['-DY=4', '-DX=Y', '-E', '-std=450core', shader] + + expected_stdout = [ + """ + void main(){ return 4;} +"""] diff --git a/glslc/test/option_dash_E.py b/glslc/test/option_dash_E.py new file mode 100644 index 000000000..b12567556 --- /dev/null +++ b/glslc/test/option_dash_E.py @@ -0,0 +1,331 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, StdinShader + + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapENoDefs(expect.StdoutMatch): + """Tests -E without any defines.""" + + shader = FileShader('void main(){}', '.vert') + expected_stdout = 'void main(){ }\n'; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEGlslFileAccepted(expect.StdoutMatch): + """Tests -E if we provide a .glsl file without an explicit stage.""" + + shader = FileShader('void main(){}', '.glsl') + expected_stdout = 'void main(){ }\n'; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapESingleDefine(expect.StdoutMatch): + """Tests -E with command-line define.""" + + shader = FileShader('void main(){ int a = X; }', '.vert') + expected_stdout = 'void main(){ int a = 4;}\n'; + glslc_args = ['-DX=4', '-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEExpansion(expect.StdoutMatch): + """Tests -E with macro expansion.""" + + shader = FileShader(''' +#define X 4 +void main() { + int a = X; +} +''', '.vert') + + expected_stdout = ''' + +void main(){ + int a = 4; +} +'''; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEFunctionMacro(expect.StdoutMatch): + """Tests -E with function-style macro expansion.""" + + shader = FileShader(''' +#define X(A) 4+A +void main() { + int a = X(1); +} +''', '.vert') + + expected_stdout = ''' + +void main(){ + int a = 4 + 1; +} +'''; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEPragma(expect.StdoutMatch): + """Tests -E to make sure pragmas get retained.""" + + shader = FileShader(''' +#pragma optimize(off) +void main() { +} +''', '.vert') + + expected_stdout = ''' +#pragma optimize(off) +void main(){ +} +'''; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEExtension(expect.StdoutMatch): + """Tests -E to make sure extensions get retained.""" + + shader = FileShader(''' +#extension foo: require +void main() { +} +''', '.vert') + + expected_stdout = ''' +#extension foo : require +void main(){ +} +'''; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapELine(expect.StdoutMatch): + """Tests -E to make sure line numbers get retained.""" + + shader = FileShader(''' +#define X 4 +#line X + +#line 2 3 + +void main() { +} +''', '.vert') + + expected_stdout = ''' + +#line 4 + +#line 2 3 + +void main(){ +} +'''; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEError(expect.ErrorMessage): + """Tests -E to make sure #errors get retained.""" + + shader = FileShader(''' +#if 1 + #error This is an error +#endif + +void main() { +} +''', '.vert') + + expected_error= [ + shader, ':3: error: \'#error\' : This is an error\n', + '1 error generated.\n'] + + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEStdin(expect.StdoutMatch): + """Tests to make sure -E works with stdin.""" + + shader = StdinShader(''' +void main() { +} +''') + + expected_stdout = ''' +void main(){ +} +'''; + glslc_args = ['-E', '-fshader-stage=vertex', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEStdinDoesNotRequireShaderStage(expect.StdoutMatch): + """Tests to make sure -E works with stdin even when no shader-stage + is specified.""" + + shader = StdinShader(''' +void main() { +} +''') + + expected_stdout = ''' +void main(){ +} +'''; + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEMultipleFiles(expect.StdoutMatch): + """Tests to make sure -E works with multiple files.""" + + shader = StdinShader(''' +void main() { +} +''') + shader2 = FileShader(''' +void function() { +} +''', '.vert') + expected_stdout = ''' +void main(){ +} + +void function(){ +} +'''; + glslc_args = ['-E', '-fshader-stage=vertex', shader, shader2] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEMultipleFilesWithoutStage(expect.StdoutMatch): + """Tests to make sure -E works with multiple files even if we do not + specify a stage.""" + + shader = StdinShader(''' +void main() { +} +''') + shader2 = FileShader(''' +void function() { +} +''', '.glsl') + expected_stdout = ''' +void main(){ +} + +void function(){ +} +'''; + glslc_args = ['-E', shader, shader2] + + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEOutputFile(expect.SuccessfulReturn, expect.ValidFileContents): + """Tests to make sure -E works with output files.""" + + shader = FileShader(''' +void function() { +} +''', '.vert') + expected_file_contents=''' +void function(){ +} +''' + target_filename='foo' + glslc_args = ['-E', shader, '-ofoo'] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEWithS(expect.StdoutMatch): + """Tests -E in the presence of -S.""" + + shader = FileShader('void main(){}', '.vert') + expected_stdout = 'void main(){ }\n'; + glslc_args = ['-E', '-S', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestMultipileDashCapE(expect.StdoutMatch): + """Tests that using -E multiple times works.""" + + shader = FileShader('void main(){}', '.vert') + expected_stdout = 'void main(){ }\n'; + glslc_args = ['-E', '-E', shader, '-E'] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEAfterFile(expect.StdoutMatch): + """Tests that using -E after the filename also works.""" + + shader = FileShader('void main(){}', '.vert') + expected_stdout = 'void main(){ }\n'; + glslc_args = [shader, '-E'] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEWithDashC(expect.StdoutMatch): + """Tests to make sure -E works in the presence of -c.""" + + shader = FileShader(''' +void main() { +} +''', '.vert') + shader2 = FileShader(''' +void function() { +} +''', '.vert') + expected_stdout = ''' +void main(){ +} + +void function(){ +} +''' + glslc_args = ['-E', '-c', shader, shader2] + + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEWithPPErrors(expect.ErrorMessage): + """Tests to make sure -E outputs error messages for preprocessing errors.""" + + shader = FileShader('''#version 310 es +#extension s enable // missing : +#defin A // Bad define +#if X // In glsl X must be defined for X to work. + // Lack of endif. +void main() { +} +''', '.vert') + expected_error = [ + shader, ':2: error: \'#extension\' : \':\' missing after extension', + ' name\n', + shader, ':3: error: \'#\' : invalid directive: defin\n', + shader, ':4: error: \'preprocessor evaluation\' : undefined macro in', + ' expression not allowed in es profile X\n', + shader, ':8: error: \'\' : missing #endif\n', + '4 errors generated.\n'] + glslc_args = ['-E', shader] + +@inside_glslc_testsuite('OptionCapE') +class TestDashCapEStdinErrors(expect.ErrorMessage): + """Tests to make sure -E outputs error messages correctly for stdin input.""" + + shader = StdinShader('''#version 310 es +#extension s enable // missing : +void main() { +} +''') + expected_error = [ + ':2: error: \'#extension\' : \':\' missing after extension', + ' name\n', + '1 error generated.\n'] + glslc_args = ['-E', shader] diff --git a/glslc/test/option_dash_S.py b/glslc/test/option_dash_S.py new file mode 100644 index 000000000..29d05572e --- /dev/null +++ b/glslc/test/option_dash_S.py @@ -0,0 +1,165 @@ +# Copyright 2015 The Shaderc 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. + +import expect +import os.path +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, StdinShader + + +def simple_vertex_shader(): + return """#version 310 es + void main() { + gl_Position = vec4(1., 2., 3., 4.); + }""" + + +def simple_fragment_shader(): + return """#version 310 es + void main() { + gl_FragDepth = 10.; + }""" + + +def simple_compute_shader(): + return """#version 310 es + void main() { + uvec3 temp = gl_WorkGroupID; + }""" + + +@inside_glslc_testsuite('OptionDashCapS') +class TestSingleDashCapSSingleFile(expect.ValidAssemblyFile): + """Tests that -S works with a single file.""" + + shader = FileShader(simple_vertex_shader(), '.vert') + glslc_args = ['-S', shader] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestSingleFileSingleDashCapS(expect.ValidAssemblyFile): + """Tests that the position of -S doesn't matter.""" + + shader = FileShader(simple_vertex_shader(), '.vert') + glslc_args = [shader, '-S'] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestSingleDashCapSMultipleFiles(expect.ValidAssemblyFile): + """Tests that -S works with multiple files.""" + + shader1 = FileShader(simple_vertex_shader(), '.vert') + shader2 = FileShader(simple_vertex_shader(), '.vert') + shader3 = FileShader(simple_fragment_shader(), '.frag') + glslc_args = ['-S', shader1, shader2, shader3] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestMultipleDashCapSSingleFile(expect.ValidAssemblyFile): + """Tests that multiple -Ss works as one.""" + + shader = FileShader(simple_vertex_shader(), '.vert') + glslc_args = ['-S', '-S', shader, '-S'] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestMultipleDashCapSMultipleFiles(expect.ValidAssemblyFile): + """Tests a mix of -Ss and files.""" + + shader1 = FileShader(simple_fragment_shader(), '.frag') + shader2 = FileShader(simple_vertex_shader(), '.vert') + shader3 = FileShader(simple_compute_shader(), '.comp') + glslc_args = ['-S', shader1, '-S', '-S', shader2, '-S', shader3, '-S'] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithDashC(expect.ValidAssemblyFile): + """Tests that -S overwrites -c.""" + + shader1 = FileShader(simple_fragment_shader(), '.frag') + shader2 = FileShader(simple_vertex_shader(), '.vert') + glslc_args = ['-c', '-S', shader1, '-c', '-c', shader2] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithDashFShaderStage(expect.ValidAssemblyFile): + """Tests that -S works with -fshader-stage=.""" + + shader1 = FileShader(simple_fragment_shader(), '.glsl') + shader2 = FileShader(simple_vertex_shader(), '.glsl') + shader3 = FileShader(simple_compute_shader(), '.glsl') + glslc_args = ['-S', + '-fshader-stage=fragment', shader1, + '-fshader-stage=vertex', shader2, + '-fshader-stage=compute', shader3] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithDashStd(expect.ValidAssemblyFileWithWarning): + """Tests that -S works with -std=.""" + + shader1 = FileShader(simple_fragment_shader(), '.frag') + shader2 = FileShader(simple_vertex_shader(), '.vert') + shader3 = FileShader(simple_compute_shader(), '.comp') + glslc_args = ['-S', '-std=450', shader1, shader2, shader3] + + w = (': warning: (version, profile) forced to be (450, none), ' + 'while in source code it is (310, es)\n') + expected_warning = [ + shader1, w, shader2, w, shader3, w, '3 warnings generated.\n'] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithDashOSingleFile(expect.SuccessfulReturn, + expect.CorrectAssemblyFilePreamble): + """Tests that -S works with -o on a single file.""" + + shader = FileShader(simple_fragment_shader(), '.frag') + glslc_args = ['-S', '-o', 'blabla', shader] + + def check_output_blabla(self, status): + output_name = os.path.join(status.directory, 'blabla') + return self.verify_assembly_file_preamble(output_name) + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithDashOMultipleFiles(expect.ErrorMessage): + """Tests that -S works with -o on a single file.""" + + shader1 = FileShader(simple_fragment_shader(), '.frag') + shader2 = FileShader(simple_vertex_shader(), '.vert') + glslc_args = ['-S', '-o', 'blabla', shader1, shader2] + + expected_error = ['glslc: error: cannot specify -o when ' + 'generating multiple output files\n'] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithStdIn(expect.ValidAssemblyFile): + """Tests that -S works with stdin.""" + + shader = StdinShader(simple_fragment_shader()) + glslc_args = ['-S', '-fshader-stage=fragment', shader] + + +@inside_glslc_testsuite('OptionDashCapS') +class TestDashCapSWithStdOut( + expect.ReturnCodeIsZero, expect.StdoutMatch, expect.StderrMatch): + """Tests that -S works with stdout.""" + + shader = FileShader(simple_fragment_shader(), '.frag') + glslc_args = ['-S', '-o', '-', shader] + + expected_stdout = True + expected_stderr = '' diff --git a/glslc/test/option_dash_c.py b/glslc/test/option_dash_c.py new file mode 100644 index 000000000..8700b32d2 --- /dev/null +++ b/glslc/test/option_dash_c.py @@ -0,0 +1,55 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader + + +def empty_es_310_shader(): + return '#version 310 es\n void main() {}\n' + + +@inside_glslc_testsuite('OptionC') +class TestSingleDashCSingleFile(expect.ValidObjectFile): + """Tests that glslc accepts -c [filename].""" + + shader = FileShader(empty_es_310_shader(), '.vert') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('OptionC') +class TestSingleFileSingleDashC(expect.ValidObjectFile): + """Tests that glslc accepts [filename] -c.""" + + shader = FileShader(empty_es_310_shader(), '.vert') + glslc_args = [shader, '-c'] + + +@inside_glslc_testsuite('OptionC') +class TestMultipleFiles(expect.ValidObjectFile): + """Tests that glslc accepts -c and multiple source files.""" + + shader1 = FileShader(empty_es_310_shader(), '.vert') + shader2 = FileShader(empty_es_310_shader(), '.frag') + glslc_args = ['-c', shader1, shader2] + + +@inside_glslc_testsuite('OptionC') +class TestMultipleDashC(expect.ValidObjectFile): + """Tests that glslc accepts multiple -c and treated them as one.""" + + shader1 = FileShader(empty_es_310_shader(), '.vert') + shader2 = FileShader(empty_es_310_shader(), '.vert') + glslc_args = ['-c', shader1, '-c', '-c', shader2] diff --git a/glslc/test/option_dash_g.py b/glslc/test/option_dash_g.py new file mode 100644 index 000000000..57654a71f --- /dev/null +++ b/glslc/test/option_dash_g.py @@ -0,0 +1,55 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader + + +def empty_es_310_shader(): + return '#version 310 es\n void main() {}\n' + + +@inside_glslc_testsuite('OptionG') +class TestSingleDashGSingleFile(expect.ValidObjectFile): + """Tests that glslc accepts -g [filename]. Need -c for now too.""" + + shader = FileShader(empty_es_310_shader(), '.vert') + glslc_args = ['-c', '-g', shader] + + +@inside_glslc_testsuite('OptionG') +class TestSingleFileSingleDashG(expect.ValidObjectFile): + """Tests that glslc accepts [filename] -g -c.""" + + shader = FileShader(empty_es_310_shader(), '.vert') + glslc_args = [shader, '-g', '-c'] + + +@inside_glslc_testsuite('OptionG') +class TestMultipleFiles(expect.ValidObjectFile): + """Tests that glslc accepts -g and multiple source files.""" + + shader1 = FileShader(empty_es_310_shader(), '.vert') + shader2 = FileShader(empty_es_310_shader(), '.frag') + glslc_args = ['-c', shader1, '-g', shader2] + + +@inside_glslc_testsuite('OptionG') +class TestMultipleDashG(expect.ValidObjectFile): + """Tests that glslc accepts multiple -g and treated them as one.""" + + shader1 = FileShader(empty_es_310_shader(), '.vert') + shader2 = FileShader(empty_es_310_shader(), '.vert') + glslc_args = ['-c', '-g', shader1, '-g', '-g', shader2] diff --git a/glslc/test/option_dash_o.py b/glslc/test/option_dash_o.py new file mode 100644 index 000000000..7037fc98f --- /dev/null +++ b/glslc/test/option_dash_o.py @@ -0,0 +1,65 @@ +# Copyright 2015 The Shaderc 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. + +import expect +import os.path +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, TempFileName + + +@inside_glslc_testsuite('OptionDashO') +class TestOptionDashOConcatenatedArg(expect.SuccessfulReturn, + expect.CorrectObjectFilePreamble): + """Tests that we can concatenate -o and the output filename.""" + + shader = FileShader('void main() {}', '.vert') + glslc_args = ['-ofoo', shader] + + def check_output_foo(self, status): + output_name = os.path.join(status.directory, 'foo') + return self.verify_object_file_preamble(output_name) + + +@inside_glslc_testsuite('OptionDashO') +class ManyOutputFilesWithDashO(expect.ErrorMessage): + """Tests -o and -c with several files generates an error.""" + + shader1 = FileShader('', '.vert') + shader2 = FileShader('', '.frag') + glslc_args = ['-o', 'foo', '-c', shader1, shader2] + expected_error = [ + 'glslc: error: cannot specify -o when ' + 'generating multiple output files\n'] + + +@inside_glslc_testsuite('OptionDashO') +class OutputFileLocation(expect.SuccessfulReturn, + expect.CorrectObjectFilePreamble): + """Tests that the -o flag puts a file in a new location.""" + + shader = FileShader('#version 310 es\nvoid main() {}', '.frag') + glslc_args = [shader, '-o', TempFileName('a.out')] + + def check_output_a_out(self, status): + output_name = os.path.join(status.directory, 'a.out') + return self.verify_object_file_preamble(output_name) + + +@inside_glslc_testsuite('OptionDashO') +class DashOMissingArgumentIsAnError(expect.ErrorMessage): + """Tests that -o without an argument is an error.""" + + glslc_args = ['-o'] + expected_error = ['glslc: error: argument to \'-o\' is missing ' + + '(expected 1 value)\n'] diff --git a/glslc/test/option_dash_x.py b/glslc/test/option_dash_x.py new file mode 100644 index 000000000..4d9b2b6b6 --- /dev/null +++ b/glslc/test/option_dash_x.py @@ -0,0 +1,96 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader + + +@inside_glslc_testsuite('OptionDashX') +class TestDashXNoArg(expect.ErrorMessage): + """Tests -x with nothing.""" + + glslc_args = ['-x'] + expected_error = [ + "glslc: error: argument to '-x' is missing (expected 1 value)\n", + 'glslc: error: no input files\n'] + + +@inside_glslc_testsuite('OptionDashX') +class TestDashXGlsl(expect.ValidObjectFile): + """Tests -x glsl.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-x', 'glsl', '-c', shader] + + +@inside_glslc_testsuite('OptionDashX') +class TestDashXWrongParam(expect.ErrorMessage): + """Tests -x with wrong parameter.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-x', 'gl', shader] + expected_error = ["glslc: error: language not recognized: 'gl'\n"] + + +@inside_glslc_testsuite('OptionDashX') +class TestMultipleDashX(expect.ValidObjectFile): + """Tests that multiple -x glsl works.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-c', '-x', 'glsl', '-x', 'glsl', shader, '-x', 'glsl'] + + +@inside_glslc_testsuite('OptionDashX') +class TestMultipleDashXCorrectWrong(expect.ErrorMessage): + """Tests -x glsl -x [wrong-language].""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-x', 'glsl', '-x', 'foo', shader] + expected_error = ["glslc: error: language not recognized: 'foo'\n"] + + +@inside_glslc_testsuite('OptionDashX') +class TestMultipleDashXWrongCorrect(expect.ErrorMessage): + """Tests -x [wrong-language] -x glsl.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-xbar', '-x', 'glsl', shader] + expected_error = ["glslc: error: language not recognized: 'bar'\n"] + + +@inside_glslc_testsuite('OptionDashX') +class TestDashXGlslConcatenated(expect.ValidObjectFile): + """Tests -xglsl.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-xglsl', shader, '-c'] + + +@inside_glslc_testsuite('OptionDashX') +class TestDashXWrongParamConcatenated(expect.ErrorMessage): + """Tests -x concatenated with a wrong language.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-xsl', shader] + expected_error = ["glslc: error: language not recognized: 'sl'\n"] + + +@inside_glslc_testsuite('OptionDashX') +class TestDashXEmpty(expect.ErrorMessage): + """Tests -x ''.""" + + shader = FileShader('void main(){}', '.vert') + glslc_args = ['-x', '', shader] + expected_error = ["glslc: error: language not recognized: ''\n"] diff --git a/glslc/test/option_shader_stage.py b/glslc/test/option_shader_stage.py new file mode 100644 index 000000000..8e0bcbb0e --- /dev/null +++ b/glslc/test/option_shader_stage.py @@ -0,0 +1,199 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader + + +def simple_vertex_shader(): + return """#version 310 es + void main() { + gl_Position = vec4(1., 2., 3., 4.); + }""" + + +def simple_fragment_shader(): + return """#version 310 es + void main() { + gl_FragDepth = 10.; + }""" + + +def simple_tessellation_control_shader(): + return """#version 440 core + layout(vertices = 3) out; + void main() { }""" + + +def simple_tessellation_evaluation_shader(): + return """#version 440 core + layout(triangles) in; + void main() { }""" + + +def simple_geometry_shader(): + return """#version 150 core + layout (triangles) in; + layout (line_strip, max_vertices = 4) out; + void main() { }""" + + +def simple_compute_shader(): + return """#version 310 es + void main() { + uvec3 temp = gl_WorkGroupID; + }""" + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageWithGlslExtension(expect.ValidObjectFile): + """Tests -fshader-stage with .glsl extension.""" + + shader = FileShader(simple_vertex_shader(), '.glsl') + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageWithKnownExtension(expect.ValidObjectFile): + """Tests -fshader-stage with known extension.""" + + shader = FileShader(simple_fragment_shader(), '.frag') + glslc_args = ['-c', '-fshader-stage=fragment', shader] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageWithUnknownExtension(expect.ValidObjectFile): + """Tests -fshader-stage with unknown extension.""" + + shader = FileShader(simple_vertex_shader(), '.unknown') + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageWithNoExtension(expect.ValidObjectFile): + """Tests -fshader-stage with no extension.""" + + shader = FileShader(simple_vertex_shader(), '') + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestAllShaderStages(expect.ValidObjectFile): + """Tests all possible -fshader-stage values.""" + + shader1 = FileShader(simple_vertex_shader(), '.glsl') + shader2 = FileShader(simple_fragment_shader(), '.glsl') + shader3 = FileShader(simple_tessellation_control_shader(), '.glsl') + shader4 = FileShader(simple_tessellation_evaluation_shader(), '.glsl') + shader5 = FileShader(simple_geometry_shader(), '.glsl') + shader6 = FileShader(simple_compute_shader(), '.glsl') + glslc_args = [ + '-c', + '-fshader-stage=vertex', shader1, + '-fshader-stage=fragment', shader2, + '-fshader-stage=tesscontrol', shader3, + '-fshader-stage=tesseval', shader4, + '-fshader-stage=geometry', shader5, + '-fshader-stage=compute', shader6] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageOverwriteFileExtension(expect.ValidObjectFile): + """Tests -fshader-stage has precedence over file extension.""" + + # a vertex shader camouflaged with .frag extension + shader = FileShader(simple_vertex_shader(), '.frag') + # Command line says it's vertex shader. Should compile successfully + # as a vertex shader. + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageLatterOverwriteFormer(expect.ValidObjectFile): + """Tests a latter -fshader-stage overwrite a former one.""" + + shader = FileShader(simple_vertex_shader(), '.glsl') + glslc_args = [ + '-c', '-fshader-stage=fragment', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageWithMultipleFiles(expect.ValidObjectFile): + """Tests -fshader-stage covers all subsequent files.""" + + shader1 = FileShader(simple_vertex_shader(), '.glsl') + # a vertex shader with .frag extension + shader2 = FileShader(simple_vertex_shader(), '.frag') + shader3 = FileShader(simple_vertex_shader(), '.a_vert_shader') + glslc_args = ['-c', '-fshader-stage=vertex', shader1, shader2, shader3] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageMultipleShaderStage(expect.ValidObjectFile): + """Tests multiple -fshader-stage.""" + + shader1 = FileShader(simple_vertex_shader(), '.glsl') + shader2 = FileShader(simple_fragment_shader(), '.frag') + shader3 = FileShader(simple_vertex_shader(), '.a_vert_shader') + glslc_args = [ + '-c', + '-fshader-stage=vertex', shader1, + '-fshader-stage=fragment', shader2, + '-fshader-stage=vertex', shader3] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestFileExtensionBeforeShaderStage(expect.ValidObjectFile): + """Tests that file extensions before -fshader-stage are not affected.""" + + # before -fshader-stage + shader1 = FileShader(simple_vertex_shader(), '.vert') + # after -fshader-stage + shader2 = FileShader(simple_fragment_shader(), '.frag') + shader3 = FileShader(simple_fragment_shader(), '.vert') + glslc_args = ['-c', shader1, '-fshader-stage=fragment', shader2, shader3] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageWrongShaderStageValue(expect.ErrorMessage): + """Tests that wrong shader stage value results in an error.""" + + shader = FileShader(simple_vertex_shader(), '.glsl') + glslc_args = ['-c', '-fshader-stage=unknown', shader] + expected_error = ["glslc: error: stage not recognized: 'unknown'\n"] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageGlslExtensionMissingShaderStage(expect.ErrorMessage): + """Tests that missing -fshader-stage for .glsl extension results in + an error.""" + + shader = FileShader(simple_vertex_shader(), '.glsl') + glslc_args = ['-c', shader] + expected_error = [ + "glslc: error: '", shader, + "': .glsl file encountered but no -fshader-stage specified ahead\n"] + + +@inside_glslc_testsuite('OptionShaderStage') +class TestShaderStageUnknownExtensionMissingShaderStage(expect.ErrorMessage): + """Tests that missing -fshader-stage for unknown extension results in + an error.""" + + shader = FileShader(simple_vertex_shader(), '.a_vert_shader') + glslc_args = ['-c', shader] + expected_error = [ + "glslc: error: '", shader, + "': file not recognized: File format not recognized\n"] diff --git a/glslc/test/option_std.py b/glslc/test/option_std.py new file mode 100644 index 000000000..873331694 --- /dev/null +++ b/glslc/test/option_std.py @@ -0,0 +1,252 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader + + +def core_vert_shader_without_version(): + # gl_ClipDistance doesn't exist in es profile (at least until 3.10). + return 'void main() { gl_ClipDistance[0] = 5.; }' + + +def core_frag_shader_without_version(): + # gl_SampleID appears in core profile from 4.00. + # gl_sampleID doesn't exsit in es profile (at least until 3.10). + return 'void main() { int temp = gl_SampleID; }' + + +@inside_glslc_testsuite('OptionStd') +class TestMissingVersionAndStd(expect.ErrorMessage): + """Tests that missing both #version and -std results in errors.""" + + shader = FileShader(core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":1: error: 'gl_SampleID' : undeclared identifier\n", + shader, ":1: error: '=' : cannot convert from 'temp float' to ", + "'temp int'\n2 errors generated.\n"] + + +@inside_glslc_testsuite('OptionStd') +class TestMissingVersionButHavingStd(expect.ValidObjectFile): + """Tests that correct -std fixes missing #version.""" + + shader = FileShader(core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', '-std=450core', shader] + + +@inside_glslc_testsuite('OptionStd') +class TestMissingVersionAndWrongStd(expect.ErrorMessage): + """Tests missing #version and wrong -std results in errors.""" + + shader = FileShader(core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', '-std=310es', shader] + expected_error = [ + shader, ":1: error: 'gl_SampleID' : undeclared identifier\n", + shader, ":1: error: '=' : cannot convert from 'temp float' to ", + "'temp mediump int'\n2 errors generated.\n"] + + +@inside_glslc_testsuite('OptionStd') +class TestConflictingVersionAndStd(expect.ValidObjectFileWithWarning): + """Tests that with both #version and -std, -std takes precedence.""" + + # Wrong #version here on purpose. + shader = FileShader( + '#version 310 es\n' + core_frag_shader_without_version(), '.frag') + # -std overwrites the wrong #version. + glslc_args = ['-c', '-std=450core', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (310, es)\n1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestMultipleStd(expect.ValidObjectFile): + """Tests that for multiple -std, the last one takes effect.""" + + shader = FileShader(core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', '-std=100', '-std=310es', shader, '-std=450core'] + + +@inside_glslc_testsuite('OptionStd') +class TestMultipleFiles(expect.ValidObjectFileWithWarning): + """Tests that -std covers all files.""" + + shader1 = FileShader(core_frag_shader_without_version(), '.frag') + shader2 = FileShader(core_vert_shader_without_version(), '.vert') + shader3 = FileShader( + '#version 310 es\n' + core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', '-std=450compatibility', shader1, shader2, shader3] + + expected_warning = [ + shader3, ': warning: (version, profile) forced to be (450, ' + 'compatibility), while in source code it is (310, es)\n' + '1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestUnkownProfile(expect.ErrorMessage): + """Tests that -std rejects unknown profile.""" + + shader = FileShader(core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', '-std=450google', shader] + expected_error = [ + "glslc: error: invalid value '450google' in '-std=450google'\n"] + + +@inside_glslc_testsuite('OptionStd') +class TestUnkownVersion(expect.ErrorMessage): + """Tests that -std rejects unknown version.""" + + shader = FileShader(core_frag_shader_without_version(), '.frag') + glslc_args = ['-c', '-std=42core', shader] + expected_error = [ + "glslc: error: invalid value '42core' in '-std=42core'\n"] + + +@inside_glslc_testsuite('OptionStd') +class TestTotallyWrongStdValue(expect.ErrorMessage): + """Tests that -std rejects totally wrong -std value.""" + + shader = FileShader(core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=wrong42', shader] + + expected_error = [ + "glslc: error: invalid value 'wrong42' in '-std=wrong42'\n"] + + +@inside_glslc_testsuite('OptionStd') +class TestVersionInsideSlashSlashComment(expect.ValidObjectFileWithWarning): + """Tests that -std substitutes the correct #version string.""" + + # The second #version string should be substituted and this shader + # should compile successfully with -std=450core. + shader = FileShader( + '// #version 310 es\n#version 310 es\n' + + core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=450core', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (310, es)\n1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestVersionInsideSlashStarComment(expect.ValidObjectFileWithWarning): + """Tests that -std substitutes the correct #version string.""" + + # The second #version string should be substituted and this shader + # should compile successfully with -std=450core. + shader = FileShader( + '/* #version 310 es */\n#version 310 es\n' + + core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=450core', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (310, es)\n1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestCommentBeforeVersion(expect.ValidObjectFileWithWarning): + """Tests that comments before #version (same line) is correctly handled.""" + + shader = FileShader( + '/* some comment */ #version 100\n' + + core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=450', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, none), while ' + 'in source code it is (100, none)\n1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestCommentAfterVersion(expect.ValidObjectFileWithWarning): + """Tests that multiple-line comments after #version is correctly handled.""" + + shader = FileShader( + '#version 100 compatibility ' + + '/* start \n second line \n end */\n' + + core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=450core', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (100, compatibility)\n1 warning generated.\n'] + + +# The following test case is disabled because of a bug in glslang. +# When checking non-newline whitespaces, glslang only recognizes +# ' ' and '\t', leaving '\v' and '\f' unhandled. The following test +# case exposes this problem. It should be turned on once a fix for +# glslang is landed. +#@inside_glslc_testsuite('OptionStd') +class TestSpaceAroundVersion(expect.ValidObjectFileWithWarning): + """Tests that space around #version is correctly handled.""" + + shader = FileShader( + '\t \t # \t \f\f version \v \t\t 310 \v\v \t es \n' + + core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=450core', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (310, es)\n1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestVersionInsideCrazyComment(expect.ValidObjectFileWithWarning): + """Tests that -std substitutes the correct #version string.""" + + # The fourth #version string should be substituted and this shader + # should compile successfully with -std=450core. + shader = FileShader( + '/* */ /* // /* #version 310 es */\n' + # /*-style comment + '// /* */ /* /* // #version 310 es\n' + # //-style comment + '///*////*//*/*/ #version 310 es*/\n' + # //-style comment + '#version 310 es\n' + core_vert_shader_without_version(), '.vert') + glslc_args = ['-c', '-std=450core', shader] + + expected_warning = [ + shader, ': warning: (version, profile) forced to be (450, core), while ' + 'in source code it is (310, es)\n1 warning generated.\n'] + + +@inside_glslc_testsuite('OptionStd') +class TestVersionMissingProfile(expect.ErrorMessage): + """Tests that missing required profile in -std results in an error.""" + + shader = FileShader('void main() {}', '.vert') + glslc_args = ['-c', '-std=310', shader] + + expected_error = [ + shader, ': error: #version: versions 300 and 310 require ', + "specifying the 'es' profile\n1 error generated.\n"] + + +@inside_glslc_testsuite('OptionStd') +class TestVersionRedudantProfile(expect.ErrorMessage): + """Tests that adding non-required profile in -std results in an error.""" + + shader = FileShader('void main() {}', '.vert') + glslc_args = ['-c', '-std=100core', shader] + + expected_error = [ + shader, ': error: #version: versions before 150 do not allow ' + 'a profile token\n1 error generated.\n'] diff --git a/glslc/test/parameter_tests.py b/glslc/test/parameter_tests.py new file mode 100644 index 000000000..456f0eba9 --- /dev/null +++ b/glslc/test/parameter_tests.py @@ -0,0 +1,162 @@ +# Copyright 2015 The Shaderc 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. + +import expect +import os.path +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, StdinShader, TempFileName + + +@inside_glslc_testsuite('File') +class SimpleFileCompiled(expect.ValidObjectFile): + """Tests whether or not a simple glsl file compiles.""" + + shader = FileShader('#version 310 es\nvoid main() {}', '.frag') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('File') +class NotSpecifyingOutputName(expect.SuccessfulReturn, + expect.CorrectObjectFilePreamble): + """Tests that when there is no -o and -E/-S/-c specified, output as a.spv.""" + + shader = FileShader('void main() {}', '.frag') + glslc_args = [shader] + + def check_output_a_spv(self, status): + output_name = os.path.join(status.directory, 'a.spv') + return self.verify_object_file_preamble(output_name) + + +@inside_glslc_testsuite('Parameters') +class HelpParameters( + expect.ReturnCodeIsZero, expect.StdoutMatch, expect.StderrMatch): + """Tests the --help flag outputs correctly and does not produce and error.""" + + glslc_args = ['--help'] + + expected_stdout = '''Usage: glslc [options] file... + +An input file of - represents standard input. + +Options: + -c Only run preprocess, compile, and assemble steps. + -Dmacro[=defn] Add an implicit macro definition. + -E Outputs only the results of the preprocessing step. + Output defaults to standard out. + -fshader-stage= + Treat subsequent input files as having stage . + Valid stages are vertex, fragment, tesscontrol, tesseval, + geometry, and compute. + -g Generate source-level debug information. + Currently this option has no effect. + --help Display available options. + -I Add directory to include search path. + -o Write output to . + A file name of '-' represents standard output. + -std= Version and profile for input files. Possible values + are concatenations of version and profile, e.g. 310es, + 450core, etc. + -S Only run preprocess and compilation steps. + -w Suppresses all warning messages. + -Werror Treat all warnings as errors. + -x Treat subsequent input files as having type . + The only supported language is glsl. +''' + + expected_stderr = '' + + +@inside_glslc_testsuite('Parameters') +class HelpIsNotTooWide(expect.StdoutNoWiderThan80Columns): + """Tests that --help output is not too wide.""" + + glslc_args = ['--help'] + + +@inside_glslc_testsuite('Parameters') +class UnknownSingleLetterArgument(expect.ErrorMessage): + """Tests that an unknown argument triggers an error message.""" + + glslc_args = ['-a'] + expected_error = ["glslc: error: unknown argument: '-a'\n"] + + +@inside_glslc_testsuite('Parameters') +class UnknownMultiLetterArgument(expect.ErrorMessage): + """Tests that an unknown argument triggers an error message.""" + + glslc_args = ['-zzz'] + expected_error = ["glslc: error: unknown argument: '-zzz'\n"] + + +@inside_glslc_testsuite('Parameters') +class UnsupportedOption(expect.ErrorMessage): + """Tests that an unsupported option triggers an error message.""" + + glslc_args = ['--unsupported-option'] + expected_error = [ + "glslc: error: unsupported option: '--unsupported-option'\n"] + + +@inside_glslc_testsuite('File') +class FileNotFound(expect.ErrorMessage): + """Tests the error message if a file cannot be found.""" + + blabla_file = TempFileName('blabla.frag') + glslc_args = [blabla_file] + expected_error = [ + "glslc: error: cannot open input file: '", blabla_file, + "': No such file or directory\n"] + + +@inside_glslc_testsuite('Unsupported') +class LinkingNotSupported(expect.ErrorMessage): + """Tests the error message generated by linking not supported yet.""" + + shader1 = FileShader('void main() {}', '.vert') + shader2 = FileShader('void main() {}', '.frag') + glslc_args = [shader1, shader2] + expected_error = [ + 'glslc: error: linking multiple files is not supported yet. ', + 'Use -c to compile files individually.\n'] + + +@inside_glslc_testsuite('Unsupported') +class MultipleStdinUnsupported(expect.ErrorMessage): + """Tests the error message generated by having more than one - input.""" + + glslc_args = ['-c', '-fshader-stage=vertex', '-', '-'] + expected_error = [ + 'glslc: error: specifying standard input "-" as input more' + ' than once is not allowed.\n'] + + +@inside_glslc_testsuite('Parameters') +class StdinWithoutShaderStage(expect.StdoutMatch, expect.StderrMatch): + """Tests that you must use -fshader-stage when specifying - as input.""" + shader = StdinShader( + """ + int a() { + } + void main() { + int x = a(); + } + """) + glslc_args = [shader] + + expected_stdout = '' + expected_stderr = [ + "glslc: error: '-': -fshader-stage required when input is from " + 'standard input "-"\n'] diff --git a/glslc/test/placeholder.py b/glslc/test/placeholder.py new file mode 100644 index 000000000..2cb8e5091 --- /dev/null +++ b/glslc/test/placeholder.py @@ -0,0 +1,117 @@ +# Copyright 2015 The Shaderc 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. + +"""A number of placeholders and their rules for expansion when used in tests. + +These placeholders, when used in glslc_args or expected_* variables of +GlslCTest, have special meanings. In glslc_args, they will be substituted by +the result of instantiate_for_glslc_args(), while in expected_*, by +instantiate_for_expectation(). A TestCase instance will be passed in as +argument to the instantiate_*() methods. +""" +import os +import tempfile + + +class PlaceHolderException(Exception): + """Exception class for PlaceHolder.""" + pass + + +class PlaceHolder(object): + """Base class for placeholders.""" + + def instantiate_for_glslc_args(self, testcase): + """Instantiation rules for glslc_args. + + This method will be called when the current placeholder appears in + glslc_args. + + Returns: + A string to replace the current placeholder in glslc_args. + """ + raise PlaceHolderException('Subclass should implement this function.') + + def instantiate_for_expectation(self, testcase): + """Instantiation rules for expected_*. + + This method will be called when the current placeholder appears in + expected_*. + + Returns: + A string to replace the current placeholder in expected_*. + """ + raise PlaceHolderException('Subclass should implement this function.') + + +class FileShader(PlaceHolder): + """Stands for a shader whose source code is in a file.""" + + def __init__(self, source, suffix): + assert isinstance(source, str) + assert isinstance(suffix, str) + self.source = source + self.suffix = suffix + self.filename = None + + def instantiate_for_glslc_args(self, testcase): + """Creates a temporary file and writes the source into it. + + Returns: + The name of the temporary file. + """ + shader, self.filename = tempfile.mkstemp( + dir=testcase.directory, suffix=self.suffix) + shader_object = os.fdopen(shader, 'w') + shader_object.write(self.source) + shader_object.close() + return self.filename + + def instantiate_for_expectation(self, testcase): + assert self.filename is not None + return self.filename + + +class StdinShader(PlaceHolder): + """Stands for a shader whose source code is from stdin.""" + + def __init__(self, source): + assert isinstance(source, str) + self.source = source + self.filename = None + + def instantiate_for_glslc_args(self, testcase): + """Writes the source code back to the TestCase instance.""" + testcase.stdin_shader = self.source + self.filename = '-' + return self.filename + + def instantiate_for_expectation(self, testcase): + assert self.filename is not None + return self.filename + + +class TempFileName(PlaceHolder): + """Stands for a temporary file's name.""" + + def __init__(self, filename): + assert isinstance(filename, str) + assert filename != '' + self.filename = filename + + def instantiate_for_glslc_args(self, testcase): + return os.path.join(testcase.directory, self.filename) + + def instantiate_for_expectation(self, testcase): + return os.path.join(testcase.directory, self.filename) diff --git a/glslc/test/pragma_shader_stage.py b/glslc/test/pragma_shader_stage.py new file mode 100644 index 000000000..be7e23e40 --- /dev/null +++ b/glslc/test/pragma_shader_stage.py @@ -0,0 +1,404 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, StdinShader + +VERTEX_ONLY_SHADER_WITH_PRAGMA = \ + """#version 310 es + #pragma shader_stage(vertex) + void main() { + gl_Position = vec4(1.); + }""" + +FRAGMENT_ONLY_SHADER_WITH_PRAGMA = \ + """#version 310 es + #pragma shader_stage(fragment) + void main() { + gl_FragDepth = 10.; + }""" + + +TESS_CONTROL_ONLY_SHADER_WITH_PRAGMA = \ + """#version 440 core + #pragma shader_stage(tesscontrol) + layout(vertices = 3) out; + void main() { }""" + + +TESS_EVAL_ONLY_SHADER_WITH_PRAGMA = \ + """#version 440 core + #pragma shader_stage(tesseval) + layout(triangles) in; + void main() { }""" + + +GEOMETRY_ONLY_SHDER_WITH_PRAGMA = \ + """#version 150 core + #pragma shader_stage(geometry) + layout (triangles) in; + layout (line_strip, max_vertices = 4) out; + void main() { }""" + + +COMPUTE_ONLY_SHADER_WITH_PRAGMA = \ + """#version 310 es + #pragma shader_stage(compute) + void main() { + uvec3 temp = gl_WorkGroupID; + }""" + +# In the following tests, +# PSS stands for PragmaShaderStage, and OSS stands for OptionShaderStage. + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithGlslExtension(expect.ValidObjectFile): + """Tests #pragma shader_stage() with .glsl extension.""" + + shader = FileShader(VERTEX_ONLY_SHADER_WITH_PRAGMA, '.glsl') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithUnkownExtension(expect.ValidObjectFile): + """Tests #pragma shader_stage() with unknown extension.""" + + shader = FileShader(VERTEX_ONLY_SHADER_WITH_PRAGMA, '.unkown') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithStdin(expect.ValidObjectFile): + """Tests #pragma shader_stage() with stdin.""" + + shader = StdinShader(VERTEX_ONLY_SHADER_WITH_PRAGMA) + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithSameShaderExtension(expect.ValidObjectFile): + """Tests that #pragma shader_stage() specifies the same stage as file + extesion.""" + + shader = FileShader(VERTEX_ONLY_SHADER_WITH_PRAGMA, '.vert') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSOverrideShaderExtension(expect.ValidObjectFile): + """Tests that #pragma shader_stage() overrides file extension.""" + + shader = FileShader(VERTEX_ONLY_SHADER_WITH_PRAGMA, '.frag') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestOSSOverridePSS(expect.ValidObjectFile): + """Tests that -fshader-stage overrides #pragma shader_stage().""" + + # wrong pragma and wrong file extension + shader = FileShader( + """#version 310 es + #pragma shader_stage(fragment) + void main() { + gl_Position = vec4(1.); + }""", '.frag') + # -fshader-stage to the rescue! ^.^ + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestMultipleSamePSS(expect.ValidObjectFile): + """Tests that multiple identical #pragma shader_stage() works.""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage(vertex) + #pragma shader_stage(vertex) + void main() { + #pragma shader_stage(vertex) + gl_Position = vec4(1.); + #pragma shader_stage(vertex) + } + #pragma shader_stage(vertex) + #pragma shader_stage(vertex) + """, '.glsl') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestConflictingPSS(expect.ErrorMessage): + """Conflicting #pragma shader_stage() directives result in an error.""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage(vertex) + void main() { + gl_Position = vec4(1.); + } + #pragma shader_stage(fragment) + """, '.glsl') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":6: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'fragment' (was 'vertex' at ", shader, ':2)\n'] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestAllPSSValues(expect.ValidObjectFile): + """Tests all possible #pragma shader_stage() values.""" + + shader1 = FileShader(VERTEX_ONLY_SHADER_WITH_PRAGMA, '.glsl') + shader2 = FileShader(FRAGMENT_ONLY_SHADER_WITH_PRAGMA, '.glsl') + shader3 = FileShader(TESS_CONTROL_ONLY_SHADER_WITH_PRAGMA, '.glsl') + shader4 = FileShader(TESS_EVAL_ONLY_SHADER_WITH_PRAGMA, '.glsl') + shader5 = FileShader(GEOMETRY_ONLY_SHDER_WITH_PRAGMA, '.glsl') + shader6 = FileShader(COMPUTE_ONLY_SHADER_WITH_PRAGMA, '.glsl') + glslc_args = ['-c', shader1, shader2, shader3, shader4, shader5, shader6] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestWrongPSSValue(expect.ErrorMessage): + """Tests that #pragma shader_stage([wrong-stage]) results in an error.""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage(superstage) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":2: error: '#pragma': invalid stage for 'shader_stage' " + "#pragma: 'superstage'\n"] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestEmptyPSSValue(expect.ErrorMessage): + """Tests that #pragma shader_stage([empty]) results in an error.""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage() + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":2: error: '#pragma': invalid stage for 'shader_stage' " + "#pragma: ''\n"] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestFirstPSSBeforeNonPPCode(expect.ErrorMessage): + """Tests that the first #pragma shader_stage() should appear before + any non-preprocessing code.""" + + shader = FileShader( + """#version 310 es + #ifndef REMOVE_UNUSED_FUNCTION + int inc(int i) { return i + 1; } + #endif + #pragma shader_stage(vertex) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":5: error: '#pragma': the first 'shader_stage' #pragma " + 'must appear before any non-preprocessing code\n'] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSMultipleErrors(expect.ErrorMessage): + """Tests that if there are multiple errors, they are all reported.""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage(idontknow) + #pragma shader_stage(vertex) + void main() { + gl_Position = vec4(1.); + } + #pragma shader_stage(fragment) + """, '.glsl') + glslc_args = ['-c', shader] + expected_error = [ + shader, ":2: error: '#pragma': invalid stage for 'shader_stage' " + "#pragma: 'idontknow'\n", + shader, ":3: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'vertex' (was 'idontknow' at ", shader, ':2)\n', + shader, ":7: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'fragment' (was 'idontknow' at ", shader, ':2)\n'] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestSpacesAroundPSS(expect.ValidObjectFile): + """Tests that spaces around #pragma shader_stage() works.""" + + shader = FileShader( + """#version 310 es + # pragma shader_stage ( vertex ) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestTabsAroundPSS(expect.ValidObjectFile): + """Tests that tabs around #pragma shader_stage() works.""" + + shader = FileShader( + """#version 310 es + \t\t#\tpragma\t\t\tshader_stage\t\t(\t\t\t\tvertex\t\t)\t\t\t\t + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithMacro(expect.ValidObjectFile): + """Tests that #pragma shader_stage() works with macros.""" + + shader = FileShader( + """#version 310 es + #if 0 + some random stuff here which can cause a problem + #else + # pragma shader_stage(vertex) + #endif + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithCmdLineMacroDef(expect.ValidObjectFile): + """Tests that macro definitions passed in from command line work.""" + + shader = FileShader( + """#version 310 es + #ifdef IS_A_VERTEX_SHADER + # pragma shader_stage(vertex) + #else + # pragma shader_stage(fragment) + #endif + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', '-DIS_A_VERTEX_SHADER', shader] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestNoMacroExpansionInsidePSS(expect.ErrorMessage): + """Tests that there is no macro expansion inside #pragma shader_stage().""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage(STAGE_FROM_CMDLINE) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', '-DSTAGE_FROM_CMDLINE=vertex', shader] + + expected_error = [ + shader, ":2: error: '#pragma': invalid stage for 'shader_stage' " + "#pragma: 'STAGE_FROM_CMDLINE'\n"] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithPoundLine310(expect.ErrorMessage): + """Tests that #pragma shader_stage() works with #line.""" + + shader = FileShader( + """#version 310 es + #pragma shader_stage(unknown) + #line 42 + #pragma shader_stage(google) + #line 100 0 + #pragma shader_stage(elgoog) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + + expected_error = [ + shader, ":2: error: '#pragma': invalid stage for 'shader_stage' " + "#pragma: 'unknown'\n", + shader, ":42: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'google' (was 'unknown' at ", shader, ':2)\n', + shader, ":100: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'elgoog' (was 'unknown' at ", shader, ':2)\n'] + + +@inside_glslc_testsuite('PragmaShaderStage') +class TestPSSWithPoundLine150(expect.ErrorMessage): + """Tests that #pragma shader_stage() works with #line. + + For older desktop versions, a #line directive specify the line number of + the #line directive, not the next line. + """ + + shader = FileShader( + """#version 150 + #pragma shader_stage(unknown) + #line 42 + #pragma shader_stage(google) + #line 100 0 + #pragma shader_stage(elgoog) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + + expected_error = [ + shader, ":2: error: '#pragma': invalid stage for 'shader_stage' " + "#pragma: 'unknown'\n", + shader, ":43: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'google' (was 'unknown' at ", shader, ':2)\n', + shader, ":101: error: '#pragma': conflicting stages for 'shader_stage' " + "#pragma: 'elgoog' (was 'unknown' at ", shader, ':2)\n'] + + +@inside_glslc_testsuite('PragmaShaderStage') +class ErrorBeforePragma(expect.ErrorMessage): + """Tests that errors before pragmas are emitted.""" + + shader = FileShader( + """#version 310 es + #something + #pragma shader_stage(vertex) + void main() { + gl_Position = vec4(1.); + } + """, '.glsl') + glslc_args = ['-c', shader] + expected_error = [shader, ':2: error: \'#\' : invalid directive:', + ' something\n' + '1 error generated.\n'] diff --git a/glslc/test/stdin_out.py b/glslc/test/stdin_out.py new file mode 100644 index 000000000..f5880a6b6 --- /dev/null +++ b/glslc/test/stdin_out.py @@ -0,0 +1,37 @@ +# Copyright 2015 The Shaderc 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. + +import expect +from glslc_test_framework import inside_glslc_testsuite +from placeholder import FileShader, StdinShader + + +@inside_glslc_testsuite('StdInOut') +class VerifyStdinWorks(expect.ValidObjectFile): + """Tests glslc accepts vertex shader extension (.vert).""" + + shader = StdinShader('void main() { }') + glslc_args = ['-c', '-fshader-stage=vertex', shader] + + +@inside_glslc_testsuite('StdInOut') +class VerifyStdoutWorks( + expect.ReturnCodeIsZero, expect.StdoutMatch, expect.StderrMatch): + + shader = FileShader('void main() {}', '.vert') + glslc_args = [shader, '-o', '-'] + + # We expect SOME stdout, we just do not care what. + expected_stdout = True + expected_stderr = '' diff --git a/libshaderc/.clang-format b/libshaderc/.clang-format new file mode 100644 index 000000000..e209e8cb8 --- /dev/null +++ b/libshaderc/.clang-format @@ -0,0 +1,5 @@ +--- +# Use Google code formatting rules. +Language: Cpp +BasedOnStyle: Google +... diff --git a/libshaderc/CMakeLists.txt b/libshaderc/CMakeLists.txt new file mode 100644 index 000000000..7b629c1d8 --- /dev/null +++ b/libshaderc/CMakeLists.txt @@ -0,0 +1,23 @@ +project(libshaderc) + +add_library(shaderc STATIC + include/shaderc.h + src/shaderc.cc + src/shaderc_private.h +) + +default_compile_options(shaderc) +target_include_directories(shaderc PUBLIC include PRIVATE ${glslang_SOURCE_DIR}) +find_package(Threads) +target_link_libraries(shaderc PRIVATE + glslang OSDependent OGLCompiler glslang ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries(shaderc PRIVATE shaderc_util) +target_link_libraries(shaderc PRIVATE SPIRV) + +add_shaderc_tests( + TEST_PREFIX shaderc + LINK_LIBS shaderc + INCLUDE_DIRS include ${glslang_SOURCE_DIR} + TEST_NAMES + shaderc + shaderc_cpp) diff --git a/libshaderc/include/shaderc.h b/libshaderc/include/shaderc.h new file mode 100644 index 000000000..c1b71ed5b --- /dev/null +++ b/libshaderc/include/shaderc.h @@ -0,0 +1,111 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef SHADERC_H_ +#define SHADERC_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +enum shaderc_shader_kind { + shaderc_glsl_vertex_shader, + shaderc_glsl_fragment_shader +}; + +// Usage examples: +// +// Aggressively release compiler resources, but spend time in initialization +// for each new use. +// shaderc_compiler_t compiler = shaderc_compiler_initialize(); +// shader_spv_module_t module = shaderc_compile_into_spv(compiler, +// "int main() {}", 13, shaderc_glsl_vertex_shader, "main"); +// // Do stuff with module compilation results. +// shaderc_module_release(module); +// shaderc_compiler_release(compiler); +// +// Keep the compiler object around for a long time, but pay for extra space +// occupied. +// shaderc_compiler_t compiler = shaderc_compiler_initialize(); +// // On the same, other or multiple simultaneous threads. +// shader_spv_module_t module = shaderc_compile_into_spv(compiler, +// "int main() {}", 13, shaderc_glsl_vertex_shader, "main"); +// // Do stuff with module compilation results. +// shaderc_module_release(module); +// // Once no more compilations are to happen. +// shaderc_compiler_release(compiler); + +// An opaque handle to an object that manages all compiler state. +typedef struct shaderc_compiler* shaderc_compiler_t; + +// Returns a shaderc_compiler_t that can be used to compile modules. +// A return of NULL indicates that there was an error initializing the compiler. +// Any function operating on shaderc_compiler_t must offer the basic +// thread-safety guarantee. +// [http://herbsutter.com/2014/01/13/gotw-95-solution-thread-safety-and-synchronization/] +// That is: concurrent invocation of these functions on DIFFERENT objects needs +// no synchronization; concurrent invocation of these functions on the SAME +// object requires synchronization IF AND ONLY IF some of them take a non-const +// argument. +shaderc_compiler_t shaderc_compiler_initialize(); + +// Releases the resources held by the shaderc_compiler_t. +// After this call it is invalid to make any future calls to functions +// involving this shaderc_compiler_t. +void shaderc_compiler_release(shaderc_compiler_t); + +// An opaque handle to the results of a call to shaderc_compile_into_spv(). +typedef struct shaderc_spv_module* shaderc_spv_module_t; + +// Takes a GLSL source string and the associated shader type, compiles it into +// SPIR-V, and returns a shaderc_spv_module that contains the results of the +// compilation. The entry_point_name null-terminated string +// defines the name of the entry point to associate with this GLSL source. +// May be safely called from multiple threads without explicit synchronization. +// If there was failure in allocating the compiler object NULL will be returned. +shaderc_spv_module_t shaderc_compile_into_spv(const shaderc_compiler_t compiler, + const char* source_text, + int source_text_size, + shaderc_shader_kind shader_kind, + const char* entry_point_name); + +// The following functions, operating on shaderc_spv_module_t objects, offer +// only the basic thread-safety guarantee. + +// Releases the resources held by module. It is invalid to use module for any +// further operations. +void shaderc_module_release(shaderc_spv_module_t module); + +// Returns true if the result in module was a successful compilation. +bool shaderc_module_get_success(const shaderc_spv_module_t module); + +// Returns the number of bytes in a SPIR-V module. +size_t shaderc_module_get_length(const shaderc_spv_module_t module); + +// Returns a pointer to the start of the SPIR-V bytes. +// This is guaranteed to be castable to a uint32_t*. +const char* shaderc_module_get_bytes(const shaderc_spv_module_t module); + +// Returns a null-terminated string that contains any error messages generated +// during the compilation. +const char* shaderc_module_get_error_message(const shaderc_spv_module_t module); + +#ifdef __cplusplus +} +#endif // __cplusplus + +#endif // SHADERC_H_ diff --git a/libshaderc/include/shaderc.hpp b/libshaderc/include/shaderc.hpp new file mode 100644 index 000000000..919b05b7c --- /dev/null +++ b/libshaderc/include/shaderc.hpp @@ -0,0 +1,118 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef SHADERC_HPP_ +#define SHADERC_HPP_ + +#include +#include + +#include "shaderc.h" + +namespace shaderc { +// Contains the result of a compilation to SPIR-V. +class SpvModule { + public: + // Upon creation, the SpvModule takes ownership of the shaderc_spv_module_t. + // During destruction of the SpvModule, the shaderc_spv_module_t will be + // released. + explicit SpvModule(shaderc_spv_module_t module) : module(module) {} + ~SpvModule() { shaderc_module_release(module); } + + SpvModule(SpvModule&& other) { + module = other.module; + other.module = nullptr; + } + + // Returns true if the module was successfully compiled. + bool GetSuccess() const { + return module && shaderc_module_get_success(module); + } + + // Returns any error message found during compilation. + std::string GetErrorMessage() const { + if (!module) { + return ""; + } + return shaderc_module_get_error_message(module); + } + + // Returns a pointer to the start of the compiled SPIR-V. + // It is guaranteed that static_cast is valid to call on this + // pointer. + // This pointer remains valid only until the SpvModule is destroyed. + const char* GetData() const { + if (!module) { + return ""; + } + return shaderc_module_get_bytes(module); + } + + // Returns the number of bytes contained in the compiled SPIR-V. + size_t GetLength() const { + if (!module) { + return 0; + } + return shaderc_module_get_length(module); + } + + private: + SpvModule(const SpvModule& other) = delete; + SpvModule& operator=(const SpvModule& other) = delete; + + shaderc_spv_module_t module; +}; + +// The compilation context for compiling source to SPIR-V. +class Compiler { + public: + Compiler() : compiler(shaderc_compiler_initialize()) {} + ~Compiler() { shaderc_compiler_release(compiler); } + + Compiler(Compiler&& other) { + compiler = other.compiler; + other.compiler = nullptr; + } + + bool IsValid() { return compiler != nullptr; } + + // Compiles the given source GLSL into a SPIR-V module. + // The source_text parameter must be a valid pointer. + // The source_text_size parameter must be the length of the source text. + // It is valid for the returned SpvModule object to outlive this compiler + // object. + SpvModule CompileGlslToSpv(const char* source_text, int source_text_size, + shaderc_shader_kind shader_kind) const { + shaderc_spv_module_t module = shaderc_compile_into_spv( + compiler, source_text, source_text_size, shader_kind, "main"); + return SpvModule(module); + } + + // Compiles the given source GLSL into a SPIR-V module by invoking + // CompileGlslToSpv(const char*, int, shaderc_shader_kind); + SpvModule CompileGlslToSpv(const std::string& source_text, + shaderc_shader_kind shader_kind) const { + return CompileGlslToSpv(source_text.data(), source_text.size(), + shader_kind); + } + + private: + Compiler(const Compiler&) = delete; + Compiler& operator=(const Compiler& other) = delete; + + shaderc_compiler_t compiler; +}; +}; + +#endif // SHADERC_HPP_ diff --git a/libshaderc/src/shaderc.cc b/libshaderc/src/shaderc.cc new file mode 100644 index 000000000..3149d8d38 --- /dev/null +++ b/libshaderc/src/shaderc.cc @@ -0,0 +1,181 @@ +// Copyright 2015 The Shaderc 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. + +#include "shaderc_private.h" + +#include +#include +#include +#include +#include + +#include "SPIRV/GlslangToSpv.h" +#include "glslang/Include/InfoSink.h" +#include "glslang/Include/ShHandle.h" +#include "glslang/MachineIndependent/localintermediate.h" +#include "glslang/Public/ShaderLang.h" + +#include "libshaderc_util/resources.h" + +#if (defined(_MSC_VER) && !defined(_CPPUNWIND)) || !defined(__EXCEPTIONS) +#define TRY_IF_EXCEPTIONS_ENABLED +#define CATCH_IF_EXCEPTIONS_ENABLED(X) if (0) +#else +#define TRY_IF_EXCEPTIONS_ENABLED try +#define CATCH_IF_EXCEPTIONS_ENABLED(X) catch (X) +#endif + +namespace { + +// Returns shader stage (ie: vertex, fragment, etc.) corresponding to kind. +EShLanguage GetStage(shaderc_shader_kind kind) { + switch (kind) { + case shaderc_glsl_vertex_shader: + return EShLangVertex; + case shaderc_glsl_fragment_shader: + return EShLangFragment; + } + assert(0 && "Unhandled shaderc_shader_kind"); + return EShLangVertex; +} + +// Produces SPIR-V binary when used in ShCompile(). +class SpvGenerator : public TCompiler { + public: + // Captures language and a valid result object that subsequent method calls + // will write to. + SpvGenerator(EShLanguage language, shaderc_spv_module* result) + : TCompiler(language, info_sink_), result_(result) {} + + // Generates SPIR-V for root, adding it to the vector captured by the + // constructor. Always returns true. + bool compile(TIntermNode* root, int version = 0, + EProfile profile = ENoProfile) override { + glslang::TIntermediate intermediate(getLanguage(), version, profile); + intermediate.setTreeRoot(root); + glslang::GlslangToSpv(intermediate, result_->spirv); + return true; // No failure-reporting mechanism above, so report success. + } + + // Adds messages accumulated during ShCompile() to the captured result. This + // can't be done during compile() because some errors will cause early exit + // without ever invoking compile(). + void CaptureMessages() { result_->messages = info_sink_.info.c_str(); } + + private: + // Where to store the compilation results. + shaderc_spv_module* result_; + // Collects info & debug messages. + TInfoSink info_sink_; +}; + +struct { + // The number of currently initialized compilers. + int compiler_initialization_count; + + std::mutex mutex; // Guards creation and deletion of glsl state. +} glsl_state = { + 0, +}; + +std::mutex compile_mutex; // Guards shaderc_compile_*. + +} // anonymous namespace + +shaderc_compiler_t shaderc_compiler_initialize() { + std::lock_guard lock(glsl_state.mutex); + ++glsl_state.compiler_initialization_count; + bool success = true; + if (glsl_state.compiler_initialization_count == 1) { + TRY_IF_EXCEPTIONS_ENABLED { success = ShInitialize(); } + CATCH_IF_EXCEPTIONS_ENABLED(...) { success = false; } + } + if (!success) { + glsl_state.compiler_initialization_count -= 1; + return nullptr; + } + shaderc_compiler_t compiler = new (std::nothrow) shaderc_compiler; + if (!compiler) { + return nullptr; + } + + return compiler; +} + +void shaderc_compiler_release(shaderc_compiler_t compiler) { + if (compiler == nullptr) { + return; + } + + if (compiler->initialized) { + compiler->initialized = false; + delete compiler; + // Defend against further dereferences through the "compiler" variable. + compiler = nullptr; + std::lock_guard lock(glsl_state.mutex); + if (glsl_state.compiler_initialization_count) { + --glsl_state.compiler_initialization_count; + if (glsl_state.compiler_initialization_count == 0) { + TRY_IF_EXCEPTIONS_ENABLED { ShFinalize(); } + CATCH_IF_EXCEPTIONS_ENABLED(...) {} + } + } + } +} + +shaderc_spv_module_t shaderc_compile_into_spv(shaderc_compiler_t compiler, + const char* source_text, + int source_text_size, + shaderc_shader_kind shader_kind, + const char* entry_point_name) { + std::lock_guard lock(compile_mutex); + shaderc_spv_module_t result = new (std::nothrow) shaderc_spv_module; + if (!result) { + return nullptr; + } + + result->compilation_succeeded = false; // In case we exit early. + if (!compiler->initialized) return result; + if (source_text_size < 0) return result; + TRY_IF_EXCEPTIONS_ENABLED { + SpvGenerator spv_generator(GetStage(shader_kind), result); + result->compilation_succeeded = ShCompile( + static_cast(&spv_generator), &source_text, + 1 /* only one string */, &source_text_size, EShOptNone, + &shaderc_util::kDefaultTBuiltInResource, 0 /* no debug options */); + spv_generator.CaptureMessages(); + } + CATCH_IF_EXCEPTIONS_ENABLED(...) { result->compilation_succeeded = false; } + return result; +} + +bool shaderc_module_get_success(const shaderc_spv_module_t module) { + return module->compilation_succeeded; +} + +size_t shaderc_module_get_length(const shaderc_spv_module_t module) { + return module->spirv.size() * sizeof(module->spirv.front()); +} + +const char* shaderc_module_get_bytes(const shaderc_spv_module_t module) { + return static_cast( + static_cast(module->spirv.data())); +} + +void shaderc_module_release(shaderc_spv_module_t module) { delete module; } + +const char* shaderc_module_get_error_message( + const shaderc_spv_module_t module) { + return module->messages.c_str(); +} diff --git a/libshaderc/src/shaderc_cpp_test.cc b/libshaderc/src/shaderc_cpp_test.cc new file mode 100644 index 000000000..4b7081687 --- /dev/null +++ b/libshaderc/src/shaderc_cpp_test.cc @@ -0,0 +1,172 @@ +// Copyright 2015 The Shaderc 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. + +#include "shaderc.hpp" + +#include +#include +#include +#include + +#include "SPIRV/spirv.h" + +namespace { + +using testing::Each; +using testing::HasSubstr; + +TEST(CppInterface, MultipleCalls) { + shaderc::Compiler compiler1, compiler2, compiler3; + EXPECT_TRUE(compiler1.IsValid()); + EXPECT_TRUE(compiler2.IsValid()); + EXPECT_TRUE(compiler3.IsValid()); +} + +TEST(CppInterface, MultipleThreadsInitializing) { + std::unique_ptr compiler1; + std::unique_ptr compiler2; + std::unique_ptr compiler3; + std::thread t1([&compiler1]() { + compiler1 = std::unique_ptr(new shaderc::Compiler()); + }); + std::thread t2([&compiler2]() { + compiler2 = std::unique_ptr(new shaderc::Compiler()); + }); + std::thread t3([&compiler3]() { + compiler3 = std::unique_ptr(new shaderc::Compiler()); + }); + t1.join(); + t2.join(); + t3.join(); + EXPECT_TRUE(compiler1->IsValid()); + EXPECT_TRUE(compiler2->IsValid()); + EXPECT_TRUE(compiler3->IsValid()); +} + +// Compiles a shader and returns true on success, false on failure. +bool CompilationSuccess(const shaderc::Compiler& compiler, + const std::string& shader, shaderc_shader_kind kind) { + return compiler.CompileGlslToSpv(shader.c_str(), shader.length(), kind) + .GetSuccess(); +} + +// Compiles a shader and returns true if the result is valid SPIR-V. +bool CompilesToValidSpv(const shaderc::Compiler& compiler, + const std::string& shader, shaderc_shader_kind kind) { + shaderc::SpvModule result = compiler.CompileGlslToSpv(shader, kind); + if (!result.GetSuccess()) return false; + size_t length = result.GetLength(); + if (length < 20) return false; + const uint32_t* bytes = + static_cast(static_cast(result.GetData())); + return bytes[0] == spv::MagicNumber; +} + +TEST(CppInterface, CompilerMoves) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + shaderc::Compiler compiler2(std::move(compiler)); + ASSERT_FALSE(compiler.IsValid()); + ASSERT_TRUE(compiler2.IsValid()); +} + +TEST(CppInterface, EmptyString) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + EXPECT_TRUE(CompilationSuccess(compiler, "", shaderc_glsl_vertex_shader)); + EXPECT_TRUE(CompilationSuccess(compiler, "", shaderc_glsl_fragment_shader)); +} + +TEST(CppInterface, ModuleMoves) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + shaderc::SpvModule result = + compiler.CompileGlslToSpv("", shaderc_glsl_vertex_shader); + EXPECT_TRUE(result.GetSuccess()); + shaderc::SpvModule result2(std::move(result)); + EXPECT_FALSE(result.GetSuccess()); + EXPECT_TRUE(result2.GetSuccess()); +} + +TEST(CppInterface, GarbageString) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + EXPECT_FALSE( + CompilationSuccess(compiler, "jfalkds", shaderc_glsl_vertex_shader)); + EXPECT_FALSE( + CompilationSuccess(compiler, "jfalkds", shaderc_glsl_fragment_shader)); +} + +TEST(CppInterface, MinimalShader) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + const std::string kMinimalShader = "void main(){}"; + EXPECT_TRUE( + CompilesToValidSpv(compiler, kMinimalShader, shaderc_glsl_vertex_shader)); + EXPECT_TRUE(CompilesToValidSpv(compiler, kMinimalShader, + shaderc_glsl_fragment_shader)); +} + +TEST(CppInterface, StdAndCString) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + const char* kMinimalShader = "void main(){}"; + shaderc::SpvModule result1 = compiler.CompileGlslToSpv( + kMinimalShader, strlen(kMinimalShader), shaderc_glsl_fragment_shader); + shaderc::SpvModule result2 = compiler.CompileGlslToSpv( + std::string(kMinimalShader), shaderc_glsl_fragment_shader); + EXPECT_TRUE(result1.GetSuccess()); + EXPECT_TRUE(result2.GetSuccess()); + EXPECT_EQ(result1.GetLength(), result2.GetLength()); + EXPECT_EQ(std::vector(result1.GetData(), + result1.GetData() + result1.GetLength()), + std::vector(result2.GetData(), + result2.GetData() + result2.GetLength())); +} + +TEST(CppInterface, ErrorsReported) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + shaderc::SpvModule result = compiler.CompileGlslToSpv( + "int f(){return wrongname;}", shaderc_glsl_vertex_shader); + ASSERT_FALSE(result.GetSuccess()); + EXPECT_THAT(result.GetErrorMessage(), HasSubstr("wrongname")); +} + +TEST(CppInterface, MultipleThreadsCalling) { + shaderc::Compiler compiler; + ASSERT_TRUE(compiler.IsValid()); + bool results[10]; + std::vector threads; + for (auto& r : results) { + threads.emplace_back([&compiler, &r]() { + r = CompilationSuccess(compiler, "void main(){}", + shaderc_glsl_vertex_shader); + }); + } + for (auto& t : threads) { + t.join(); + } + EXPECT_THAT(results, Each(true)); +} + +TEST(CppInterface, AccessorsOnNullModule) { + shaderc::SpvModule result(nullptr); + EXPECT_FALSE(result.GetSuccess()); + EXPECT_EQ(std::string(), result.GetErrorMessage()); + EXPECT_EQ(std::string(), result.GetData()); + EXPECT_EQ(0u, result.GetLength()); +} + +} // anonymous namespace diff --git a/libshaderc/src/shaderc_private.h b/libshaderc/src/shaderc_private.h new file mode 100644 index 000000000..e01eef0bc --- /dev/null +++ b/libshaderc/src/shaderc_private.h @@ -0,0 +1,39 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_SRC_SHADERC_PRIVATE_H_ +#define LIBSHADERC_SRC_SHADERC_PRIVATE_H_ + +#include "shaderc.h" + +#include +#include +#include + +// Described in shaderc.h. +struct shaderc_spv_module { + // Whether compilation succeeded. + bool compilation_succeeded; + // SPIR-V binary. + std::vector spirv; + // Compilation messages. + std::string messages; +}; + +struct shaderc_compiler { + // Whether or not this compiler is in an initialized state. + bool initialized = true; +}; + +#endif // LIBSHADERC_SRC_SHADERC_PRIVATE_H_ diff --git a/libshaderc/src/shaderc_test.cc b/libshaderc/src/shaderc_test.cc new file mode 100644 index 000000000..8aca445c5 --- /dev/null +++ b/libshaderc/src/shaderc_test.cc @@ -0,0 +1,189 @@ +// Copyright 2015 The Shaderc 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. + +#include "shaderc.h" + +#include +#include +#include + +#include "SPIRV/spirv.h" + +namespace { + +using testing::Each; +using testing::HasSubstr; + +TEST(Init, MultipleCalls) { + shaderc_compiler_t compiler1, compiler2, compiler3; + EXPECT_NE(nullptr, compiler1 = shaderc_compiler_initialize()); + EXPECT_NE(nullptr, compiler2 = shaderc_compiler_initialize()); + EXPECT_NE(nullptr, compiler3 = shaderc_compiler_initialize()); + shaderc_compiler_release(compiler1); + shaderc_compiler_release(compiler2); + shaderc_compiler_release(compiler3); +} + +TEST(Init, MultipleThreadsCalling) { + shaderc_compiler_t compiler1, compiler2, compiler3; + std::thread t1([&compiler1]() { compiler1 = shaderc_compiler_initialize(); }); + std::thread t2([&compiler2]() { compiler2 = shaderc_compiler_initialize(); }); + std::thread t3([&compiler3]() { compiler3 = shaderc_compiler_initialize(); }); + t1.join(); + t2.join(); + t3.join(); + EXPECT_NE(nullptr, compiler1); + EXPECT_NE(nullptr, compiler2); + EXPECT_NE(nullptr, compiler3); + shaderc_compiler_release(compiler1); + shaderc_compiler_release(compiler2); + shaderc_compiler_release(compiler3); +} + +// RAII class for shaderc_spv_module. +class Compilation { + public: + // Compiles shader, keeping the result. + Compilation(const shaderc_compiler_t compiler, const std::string& shader, + shaderc_shader_kind kind) + : compiled_result_(shaderc_compile_into_spv(compiler, shader.c_str(), + shader.size(), kind, "")) {} + + ~Compilation() { shaderc_module_release(compiled_result_); } + + shaderc_spv_module_t result() { return compiled_result_; } + + private: + shaderc_spv_module_t compiled_result_; +}; + +// RAII class for shaderc_compiler_t +class Compiler { + public: + Compiler() { compiler = shaderc_compiler_initialize(); } + ~Compiler() { shaderc_compiler_release(compiler); } + shaderc_compiler_t get_compiler_handle() { return compiler; } + + private: + shaderc_compiler_t compiler; +}; + +// Compiles a shader and returns true on success, false on failure. +bool CompilationSuccess(const shaderc_compiler_t compiler, + const std::string& shader, shaderc_shader_kind kind) { + return shaderc_module_get_success( + Compilation(compiler, shader, kind).result()); +} + +// Compiles a shader and returns true if the result is valid SPIR-V. +bool CompilesToValidSpv(const shaderc_compiler_t compiler, + const std::string& shader, shaderc_shader_kind kind) { + Compilation comp(compiler, shader, kind); + auto result = comp.result(); + if (!shaderc_module_get_success(result)) return false; + size_t length = shaderc_module_get_length(result); + if (length < 20) return false; + const uint32_t* bytes = static_cast( + static_cast(shaderc_module_get_bytes(result))); + return bytes[0] == spv::MagicNumber; +} + +TEST(CompileString, EmptyString) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + EXPECT_TRUE(CompilationSuccess(compiler.get_compiler_handle(), "", + shaderc_glsl_vertex_shader)); + EXPECT_TRUE(CompilationSuccess(compiler.get_compiler_handle(), "", + shaderc_glsl_fragment_shader)); +} + +TEST(CompileString, FailsWithCleanedUpCompiler) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + EXPECT_TRUE(CompilationSuccess(compiler.get_compiler_handle(), "", + shaderc_glsl_vertex_shader)); + EXPECT_TRUE(CompilationSuccess(compiler.get_compiler_handle(), "", + shaderc_glsl_fragment_shader)); +} + +TEST(CompileString, GarbageString) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + EXPECT_FALSE(CompilationSuccess(compiler.get_compiler_handle(), "jfalkds", + shaderc_glsl_vertex_shader)); + EXPECT_FALSE(CompilationSuccess(compiler.get_compiler_handle(), "jfalkds", + shaderc_glsl_fragment_shader)); +} + +TEST(CompileString, ReallyLongShader) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + std::string minimal_shader = ""; + minimal_shader += "void foo(){}"; + minimal_shader.append(1024 * 1024 * 8, ' '); // 8MB of spaces. + minimal_shader += "void main(){}"; + EXPECT_TRUE(CompilesToValidSpv(compiler.get_compiler_handle(), minimal_shader, + shaderc_glsl_vertex_shader)); + EXPECT_TRUE(CompilesToValidSpv(compiler.get_compiler_handle(), minimal_shader, + shaderc_glsl_fragment_shader)); +} + +TEST(CompileString, MinimalShader) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + const std::string kMinimalShader = "void main(){}"; + EXPECT_TRUE(CompilesToValidSpv(compiler.get_compiler_handle(), kMinimalShader, + shaderc_glsl_vertex_shader)); + EXPECT_TRUE(CompilesToValidSpv(compiler.get_compiler_handle(), kMinimalShader, + shaderc_glsl_fragment_shader)); +} + +TEST(CompileString, ShaderKindRespected) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + const std::string kVertexShader = "vec4 foo(){return gl_Position;}"; + EXPECT_TRUE(CompilationSuccess(compiler.get_compiler_handle(), kVertexShader, + shaderc_glsl_vertex_shader)); + EXPECT_FALSE(CompilationSuccess(compiler.get_compiler_handle(), kVertexShader, + shaderc_glsl_fragment_shader)); +} + +TEST(CompileString, ErrorsReported) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + Compilation comp(compiler.get_compiler_handle(), "int f(){return wrongname;}", + shaderc_glsl_vertex_shader); + ASSERT_FALSE(shaderc_module_get_success(comp.result())); + EXPECT_THAT(shaderc_module_get_error_message(comp.result()), + HasSubstr("wrongname")); +} + +TEST(CompileString, MultipleThreadsCalling) { + Compiler compiler; + ASSERT_NE(nullptr, compiler.get_compiler_handle()); + bool results[10]; + std::vector threads; + for (auto& r : results) { + threads.emplace_back([&compiler, &r]() { + r = CompilationSuccess(compiler.get_compiler_handle(), "void main(){}", + shaderc_glsl_vertex_shader); + }); + } + for (auto& t : threads) { + t.join(); + } + EXPECT_THAT(results, Each(true)); +} + +} // anonymous namespace diff --git a/libshaderc_util/.clang-format b/libshaderc_util/.clang-format new file mode 100644 index 000000000..e209e8cb8 --- /dev/null +++ b/libshaderc_util/.clang-format @@ -0,0 +1,5 @@ +--- +# Use Google code formatting rules. +Language: Cpp +BasedOnStyle: Google +... diff --git a/libshaderc_util/CMakeLists.txt b/libshaderc_util/CMakeLists.txt new file mode 100644 index 000000000..957cefc6a --- /dev/null +++ b/libshaderc_util/CMakeLists.txt @@ -0,0 +1,37 @@ +project(libshaderc_util) + +add_library(shaderc_util STATIC + include/libshaderc_util/file_finder.h + include/libshaderc_util/format.h + include/libshaderc_util/io.h + include/libshaderc_util/resources.h + include/libshaderc_util/string_piece.h + include/libshaderc_util/universal_unistd.h + src/file_finder.cc + src/io.cc + src/resources.cc +) + +default_compile_options(shaderc_util) +target_include_directories(shaderc_util PUBLIC include PRIVATE ${glslang_SOURCE_DIR}) +find_package(Threads) +target_link_libraries(shaderc_util PRIVATE + glslang OSDependent OGLCompiler glslang ${CMAKE_THREAD_LIBS_INIT}) + +add_shaderc_tests( + TEST_PREFIX shaderc_util + LINK_LIBS shaderc_util + TEST_NAMES + string_piece + format + file_finder + io) + +# This target copies content of testdata into the build directory. +add_custom_target(testdata COMMAND + ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/testdata/copy-to-build.cmake) + +add_dependencies(shaderc_util_file_finder_test testdata) +add_dependencies(shaderc_util_io_test testdata) + +add_definitions(-DCURRENT_DIR="${CMAKE_CURRENT_BINARY_DIR}") diff --git a/libshaderc_util/include/libshaderc_util/file_finder.h b/libshaderc_util/include/libshaderc_util/file_finder.h new file mode 100644 index 000000000..8aec7885a --- /dev/null +++ b/libshaderc_util/include/libshaderc_util/file_finder.h @@ -0,0 +1,53 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_UTIL_SRC_FILE_FINDER_H_ +#define LIBSHADERC_UTIL_SRC_FILE_FINDER_H_ + +#include +#include + +namespace shaderc_util { + +// Finds files within a search path. +class FileFinder { + public: + // Searches for a read-openable file based on filename, which must be + // non-empty. The search is attempted on filename prefixed by each element of + // search_path() in turn. The first hit is returned, or an empty string if + // there are no hits. Search attempts treat their argument the way + // std::fopen() treats its filename argument, blind to whether the path is + // absolute or relative. + // + // If a search_path() element is non-empty and not ending in a slash, then a + // slash is inserted between it and filename before its search attempt. An + // empty string in search_path() means that the filename is tried as-is. + // + // Usage advice: when searching #include files, you almost certainly want "" + // to be the first element in search_path(). That way both relative and + // absolute filenames will work as expected. Note that a "." entry on the + // search path may be prepended to an absolute filename (eg, "/a/b/c") to + // create a relative result (eg, ".//a/b/c"). + std::string FindReadableFilepath(const std::string& filename) const; + + // Search path for Find(). Users may add/remove elements as desired. + std::vector& search_path() { return search_path_; } + + private: + std::vector search_path_; +}; + +} // namespace shaderc_util + +#endif // LIBSHADERC_UTIL_SRC_FILE_FINDER_H_ diff --git a/libshaderc_util/include/libshaderc_util/format.h b/libshaderc_util/include/libshaderc_util/format.h new file mode 100644 index 000000000..182f05027 --- /dev/null +++ b/libshaderc_util/include/libshaderc_util/format.h @@ -0,0 +1,36 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_UTIL_FORMAT_H_ +#define LIBSHADERC_UTIL_FORMAT_H_ + +#include + +namespace shaderc_util { + +// Returns a string containing for every +// key-value entry in map. +template +std::string format(const Map& map, const std::string& prefix, + const std::string& infix, const std::string& postfix) { + std::stringstream s; + for (const auto& pair : map) { + s << prefix << pair.first << infix << pair.second << postfix; + } + return s.str(); +} + +} // namespace shaderc_util + +#endif // LIBSHADERC_UTIL_FORMAT_H_ diff --git a/libshaderc_util/include/libshaderc_util/io.h b/libshaderc_util/include/libshaderc_util/io.h new file mode 100644 index 000000000..0d93b2add --- /dev/null +++ b/libshaderc_util/include/libshaderc_util/io.h @@ -0,0 +1,38 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_UTIL_IO_H_ +#define LIBSHADERC_UTIL_IO_H_ + +#include +#include + +#include "string_piece.h" + +namespace shaderc_util { + +// Reads all of the characters in a given file into input_data. Outputs an +// error message to std::cerr if the file could not be read and returns false if +// there was an error. If the input_file is "-", then input is read from +// std::cin. +bool ReadFile(const std::string& input_file_name, + std::vector* input_data); + +// Writes output_data to a file, overwriting if it exists. If output_file_name +// is "-", writes to std::cout. +bool WriteFile(const std::string& output_file_name, string_piece output_data); + +} // namespace shaderc_util + +#endif // LIBSHADERC_UTIL_IO_H_ diff --git a/libshaderc_util/include/libshaderc_util/resources.h b/libshaderc_util/include/libshaderc_util/resources.h new file mode 100644 index 000000000..5b185b861 --- /dev/null +++ b/libshaderc_util/include/libshaderc_util/resources.h @@ -0,0 +1,27 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_UTIL_RESOURCES_H_ +#define LIBSHADERC_UTIL_RESOURCES_H_ + +struct TBuiltInResource; + +namespace shaderc_util { + +// A set of suitable defaults. +extern const TBuiltInResource kDefaultTBuiltInResource; + +} // namespace shaderc_util + +#endif // LIBSHADERC_UTIL_RESOURCES_H_ diff --git a/libshaderc_util/include/libshaderc_util/string_piece.h b/libshaderc_util/include/libshaderc_util/string_piece.h new file mode 100644 index 000000000..f50681786 --- /dev/null +++ b/libshaderc_util/include/libshaderc_util/string_piece.h @@ -0,0 +1,344 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_UTIL_STRING_PIECE_H_ +#define LIBSHADERC_UTIL_STRING_PIECE_H_ + +#include +#include +#include +#include + +namespace shaderc_util { +// Provides a read-only view into a string (cstring or std::string). +// This must be created after the string in question, and cannot +// outlive the memory of the string in question. +// Any operations that may modify the location or size of the +// original data render the associated string_piece invalid. + +class string_piece { + public: + typedef const char* iterator; + static const size_t npos = -1; + + string_piece() {} + + string_piece(const char* begin, const char* end) : begin_(begin), end_(end) { + assert(begin != nullptr && end != nullptr && + "string_piece must not be initialized with an nullptr"); + } + + string_piece(const char* string) { + begin_ = string; + end_ = string + strlen(string); + } + + string_piece(const std::string& str) { + if (!str.empty()) { + begin_ = &(str.front()); + end_ = &(str.back()) + 1; + } + } + + string_piece(const string_piece& other) { + begin_ = other.begin_; + end_ = other.end_; + } + + // Clears the string_piece removing any reference to the original string. + void clear() { + begin_ = nullptr; + end_ = nullptr; + } + + // Returns a pointer to the data contained in the underlying string. + // If there is no underlying string, returns a nullptr. + const char* data() const { return begin_; } + + // Returns an std::string copy of the internal data. + std::string str() const { return std::string(begin_, end_); } + + // Returns a string_piece that points to a substring in the original string. + string_piece substr(size_t pos, size_t len = npos) const { + assert(len == npos || pos + len <= size()); + return string_piece(begin_ + pos, len == npos ? end_ : begin_ + pos + len); + } + + // Takes any function object predicate that takes a char and returns a + // boolean. + // Returns the index of the first element that does not return true for the + // predicate. + // Returns string_piece::npos if all elements match. + template + size_t find_first_not_matching(T callee) { + for (auto it = begin_; it != end_; ++it) { + if (!callee(*it)) { + return it - begin_; + } + } + return npos; + } + + // Returns the index of the first character that does not match any character + // in the input string_piece. + // The search only includes characters at or after position pos. + // Returns string_piece::npos if all match. + size_t find_first_not_of(const string_piece& to_search, + size_t pos = 0) const { + if (pos >= size()) { + return npos; + } + for (auto it = begin_ + pos; it != end_; ++it) { + if (to_search.find_first_of(*it) == npos) { + return it - begin_; + } + } + return npos; + } + + // Returns find_first_not_of(str, pos) where str is a string_piece + // containing only to_search. + size_t find_first_not_of(char to_search, size_t pos = 0) const { + return find_first_not_of(string_piece(&to_search, &to_search + 1), pos); + } + + // Returns the index of the first character that matches any character in the + // input string_piece. + // The search only includes characters at or after position pos. + // Returns string_piece::npos if there is no match. + size_t find_first_of(const string_piece& to_search, size_t pos = 0) const { + if (pos >= size()) { + return npos; + } + for (auto it = begin_ + pos; it != end_; ++it) { + for (char c : to_search) { + if (c == *it) { + return it - begin_; + } + } + } + return npos; + } + + // Returns find_first_of(str, pos) where str is a string_piece + // containing only to_search. + size_t find_first_of(char to_search, size_t pos = 0) const { + return find_first_of(string_piece(&to_search, &to_search + 1), pos); + } + + // Returns the index of the last character that matches any character in the + // input string_piece. + // The search only includes characters at or before position pos. + // Returns string_piece::npos if there is no match. + size_t find_last_of(const string_piece& to_search, size_t pos = npos) const { + if (empty()) return npos; + if (pos >= size()) { + pos = size(); + } + auto it = begin_ + pos + 1; + do { + --it; + if (to_search.find_first_of(*it) != npos) { + return it - begin_; + } + } while (it != begin_); + return npos; + } + + // Returns find_last_of(str, pos) where str is a string_piece + // containing only to_search. + size_t find_last_of(char to_search, size_t pos = npos) const { + return find_last_of(string_piece(&to_search, &to_search + 1), pos); + } + + // Returns the index of the last character that does not match any character + // in the input string_piece. + // The search only includes characters at or before position pos. + // Returns string_piece::npos if there is no match. + size_t find_last_not_of(const string_piece& to_search, + size_t pos = npos) const { + if (empty()) return npos; + if (pos >= size()) { + pos = size(); + } + auto it = begin_ + pos + 1; + do { + --it; + if (to_search.find_first_of(*it) == npos) { + return it - begin_; + } + } while (it != begin_); + return npos; + } + + // Returns find_last_not_of(str, pos) where str is a string_piece + // containing only to_search. + size_t find_last_not_of(char to_search, size_t pos = 0) const { + return find_last_not_of(string_piece(&to_search, &to_search + 1), pos); + } + + // Continuously removes characters appearing in chars_to_strip from the left. + string_piece lstrip(const string_piece& chars_to_strip) const { + iterator begin = begin_; + for (; begin < end_; ++begin) + if (chars_to_strip.find_first_of(*begin) == npos) break; + if (begin >= end_) return string_piece(); + return string_piece(begin, end_); + } + + // Continuously removes characters appearing in chars_to_strip from the right. + string_piece rstrip(const string_piece& chars_to_strip) const { + iterator end = end_; + for (; begin_ < end; --end) + if (chars_to_strip.find_first_of(*(end - 1)) == npos) break; + if (begin_ >= end) return string_piece(); + return string_piece(begin_, end); + } + + // Continuously removes characters appearing in chars_to_strip from both + // sides. + string_piece strip(const string_piece& chars_to_strip) const { + return lstrip(chars_to_strip).rstrip(chars_to_strip); + } + + string_piece strip_whitespace() const { return strip(" \t\n\r\f\v"); } + + // Returns the character at index i in the string_piece. + const char& operator[](size_t i) const { return *(begin_ + i); } + + // Standard comparison operator. + bool operator==(const string_piece& other) const { + // Either end_ and _begin_ are nullptr or neither of them are. + assert(((end_ == nullptr) == (begin_ == nullptr))); + assert(((other.end_ == nullptr) == (other.begin_ == nullptr))); + if (size() != other.size()) { + return false; + } + return (memcmp(begin_, other.begin_, end_ - begin_) == 0); + } + + bool operator!=(const string_piece& other) const { + return !operator==(other); + } + + // Returns an iterator to the first element. + iterator begin() const { return begin_; } + + // Returns an iterator to one past the last element. + iterator end() const { return end_; } + + // Returns true is this string_piece starts with the same + // characters as other. + bool starts_with(const string_piece& other) const { + const char* iter = begin_; + const char* other_iter = other.begin(); + while (iter != end_ && other_iter != other.end()) { + if (*iter++ != *other_iter++) { + return false; + } + } + return other_iter == other.end(); + } + + // Returns the index of the start of the first substring that matches + // the input string_piece. + // The search only includes substrings starting at or after position pos. + // Returns npos if the string cannot be found. + size_t find(const string_piece& substr, size_t pos = 0) const { + if (empty()) return npos; + if (pos >= size()) return npos; + if (substr.empty()) return 0; + for (auto it = begin_ + pos; + end() - it >= static_cast(substr.size()); ++it) { + if (string_piece(it, end()).starts_with(substr)) return it - begin_; + } + return npos; + } + + // Returns the index of the start of the first character that matches + // the input character. + // The search only includes substrings starting at or after position pos. + // Returns npos if the character cannot be found. + size_t find(char character, size_t pos = 0) const { + return find_first_of(character, pos); + } + + // Returns true if the string_piece is empty. + bool empty() const { return begin_ == end_; } + + // Returns the number of characters in the string_piece. + size_t size() const { return end_ - begin_; } + + // Returns a vector of string_pieces representing delimiter delimited + // fields found. If the keep_delimiter parameter is true, then each + // delimiter character is kept with the string to its left. + std::vector get_fields(char delimiter, + bool keep_delimiter = false) const { + std::vector fields; + size_t first = 0; + size_t field_break = find_first_of(delimiter); + while (field_break != npos) { + fields.push_back(substr(first, field_break - first + keep_delimiter)); + first = field_break + 1; + field_break = find_first_of(delimiter, first); + } + if (size() - first > 0) { + fields.push_back(substr(first, size() - first)); + } + return fields; + } + + friend std::ostream& operator<<(std::ostream& os, const string_piece& piece); + + private: + // It is expected that begin_ and end_ will both be null or + // they will both point to valid pieces of memory, but it is invalid + // to have one of them being nullptr and the other not. + string_piece::iterator begin_ = nullptr; + string_piece::iterator end_ = nullptr; +}; + +inline std::ostream& operator<<(std::ostream& os, const string_piece& piece) { + // Either end_ and _begin_ are nullptr or neither of them are. + assert(((piece.end_ == nullptr) == (piece.begin_ == nullptr))); + if (piece.end_ != piece.begin_) { + os.write(piece.begin_, piece.end_ - piece.begin_); + } + return os; +} + +inline bool operator==(const char* first, const string_piece second) { + return second == first; +} + +inline bool operator!=(const char* first, const string_piece second) { + return !operator==(first, second); +} +} + +namespace std { +template <> +struct hash { + size_t operator()(const shaderc_util::string_piece& piece) const { + // djb2 algorithm. + size_t hash = 5381; + for (char c : piece) { + hash = ((hash << 5) + hash) + c; + } + return hash; + } +}; +} + +#endif // LIBSHADERC_UTIL_STRING_PIECE_H_ diff --git a/libshaderc_util/include/libshaderc_util/universal_unistd.h b/libshaderc_util/include/libshaderc_util/universal_unistd.h new file mode 100644 index 000000000..bd14c5a70 --- /dev/null +++ b/libshaderc_util/include/libshaderc_util/universal_unistd.h @@ -0,0 +1,32 @@ +// Copyright 2015 The Shaderc 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. + +#ifndef LIBSHADERC_UTIL_UNIVERSAL_UNISTD_H_ +#define LIBSHADERC_UTIL_UNIVERSAL_UNISTD_H_ + +#ifndef _MSC_VER +#include +#else +// Minimal set of needed to compile on windows. + +#include +#define access _access + +// https://msdn.microsoft.com/en-us/library/1w06ktdy.aspx +// Defines these constants. +#define R_OK 4 +#define W_OK 2 +#endif //_MSC_VER + +#endif // LIBSHADERC_UTIL_UNIVERSAL_UNISTD_H_ \ No newline at end of file diff --git a/libshaderc_util/src/file_finder.cc b/libshaderc_util/src/file_finder.cc new file mode 100644 index 000000000..7ef8ffb82 --- /dev/null +++ b/libshaderc_util/src/file_finder.cc @@ -0,0 +1,48 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/file_finder.h" + +#include +#include +#include + +namespace { + +// Returns "" if path is empty or ends in '/'. Otherwise, returns "/". +std::string MaybeSlash(const std::string path) { + if (path.empty() || path.back() == '/') + return ""; + else + return "/"; +} + +} // anonymous namespace + +namespace shaderc_util { + +std::string FileFinder::FindReadableFilepath( + const std::string& filename) const { + assert(!filename.empty()); + static const auto for_reading = std::ios_base::in; + std::filebuf opener; + for (const auto& prefix : search_path_) { + const std::string prefixed_filename = + prefix + MaybeSlash(prefix) + filename; + if (opener.open(prefixed_filename, for_reading)) return prefixed_filename; + } + return ""; +} + +} // namespace shaderc_util diff --git a/libshaderc_util/src/file_finder_test.cc b/libshaderc_util/src/file_finder_test.cc new file mode 100644 index 000000000..a0837a026 --- /dev/null +++ b/libshaderc_util/src/file_finder_test.cc @@ -0,0 +1,124 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/file_finder.h" + +#include + +namespace { + +using shaderc_util::FileFinder; + +const std::string kCurrentDir = CURRENT_DIR; // A macro set by cmake. + +class FileFinderTest : public testing::Test { + protected: + FileFinder finder; +}; + +TEST_F(FileFinderTest, PathStartsEmpty) { + EXPECT_TRUE(FileFinder().search_path().empty()); +} + +TEST_F(FileFinderTest, EmptyPath) { + finder.search_path().clear(); + EXPECT_EQ("", finder.FindReadableFilepath("include_file.1")); +} + +TEST_F(FileFinderTest, EmptyStringInPath) { + finder.search_path() = {""}; + EXPECT_EQ("include_file.1", finder.FindReadableFilepath("include_file.1")); + EXPECT_EQ("dir/subdir/include_file.2", + finder.FindReadableFilepath("dir/subdir/include_file.2")); +} + +TEST_F(FileFinderTest, SimplePath) { + finder.search_path() = {"dir"}; + EXPECT_EQ("dir/subdir/include_file.2", + finder.FindReadableFilepath("subdir/include_file.2")); +} + +TEST_F(FileFinderTest, PathEndsInSlash) { + finder.search_path() = {"dir/"}; + EXPECT_EQ("dir/subdir/include_file.2", + finder.FindReadableFilepath("subdir/include_file.2")); +} + +TEST_F(FileFinderTest, ParentDir) { + finder.search_path() = {"dir"}; + EXPECT_EQ("dir/../include_file.1", + finder.FindReadableFilepath("../include_file.1")); +} + +TEST_F(FileFinderTest, EntirePathIsActive) { + finder.search_path() = {"", "dir/subdir/"}; + EXPECT_EQ("include_file.1", finder.FindReadableFilepath("include_file.1")); + EXPECT_EQ("dir/subdir/include_file.2", + finder.FindReadableFilepath("include_file.2")); +} + +TEST_F(FileFinderTest, NonExistingFile) { + finder.search_path() = {"", "dir/subdir/"}; + EXPECT_EQ("", finder.FindReadableFilepath("garbage.xyxyxyxyxyxz")); +} + +TEST_F(FileFinderTest, FirstHitReturned) { + finder.search_path() = {".", "", "dir/../"}; + EXPECT_EQ("./include_file.1", finder.FindReadableFilepath("include_file.1")); +} + +TEST_F(FileFinderTest, IrrelevantPaths) { + finder.search_path() = {".", "garbage.xyxyxyxyxyz", "dir/../"}; + EXPECT_EQ("", finder.FindReadableFilepath("include_file.2")); + finder.search_path().push_back("dir/subdir"); + EXPECT_EQ("dir/subdir/include_file.2", + finder.FindReadableFilepath("include_file.2")); +} + +TEST_F(FileFinderTest, CurrentDirectory) { + ASSERT_GE(kCurrentDir.size(), 0u); + // Either the directory should start with / (if we are on Linux), + // Or it should beither X:/ or X:\ or // (if we are on Windows). + ASSERT_TRUE(kCurrentDir.front() == '\\' || kCurrentDir.front() == '/' || + (kCurrentDir.size() >= 3u && kCurrentDir[1] == ':' && + (kCurrentDir[2] == '\\' || kCurrentDir[2] == '/'))); +} + +TEST_F(FileFinderTest, AbsolutePath) { + ASSERT_NE('/', kCurrentDir.back()); + finder.search_path() = {kCurrentDir}; + EXPECT_EQ(kCurrentDir + "/include_file.1", + finder.FindReadableFilepath("include_file.1")); + EXPECT_EQ(kCurrentDir + "/dir/subdir/include_file.2", + finder.FindReadableFilepath("dir/subdir/include_file.2")); +} + +TEST_F(FileFinderTest, AbsoluteFilename) { + ASSERT_NE('/', kCurrentDir.back()); + + finder.search_path() = {""}; + const std::string absolute_file1 = kCurrentDir + "/include_file.1"; + EXPECT_EQ(absolute_file1, finder.FindReadableFilepath(absolute_file1)); + EXPECT_EQ("", finder.FindReadableFilepath("/dir/subdir/include_file.2")); + + finder.search_path().push_back("."); + EXPECT_EQ(".//dir/subdir/include_file.2", + finder.FindReadableFilepath("/dir/subdir/include_file.2")); +} + +TEST(FileFinderDeathTest, EmptyFilename) { + EXPECT_DEBUG_DEATH(FileFinder().FindReadableFilepath(""), "Assertion"); +} + +} // anonymous namespace diff --git a/libshaderc_util/src/format_test.cc b/libshaderc_util/src/format_test.cc new file mode 100644 index 000000000..4d6b43f3e --- /dev/null +++ b/libshaderc_util/src/format_test.cc @@ -0,0 +1,102 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/format.h" + +#include +#include +#include +#include + +namespace { + +using testing::AllOf; +using testing::HasSubstr; +using testing::IsEmpty; + +class FormatMap : public testing::Test { + public: + FormatMap() { + map1 = std::map{{"one", 1}}; + umap1 = std::unordered_map{map1.begin(), map1.end()}; + map8 = std::map{{1, "one"}, {2, "two"}, {3, "three"}, + {4, "four"}, {5, "five"}, {6, "six"}, + {7, "seven"}, {8, "eight"}}; + + umap8 = std::unordered_map{map8.begin(), map8.end()}; + mmap = std::multimap{{1, 100}, {1, 200}, {2, 100}, {2, 200}}; + ummap = std::unordered_multimap{mmap.begin(), mmap.end()}; + } + + protected: + std::map empty_map; + std::unordered_map empty_umap; + std::map map1; + std::unordered_map umap1; + std::map map8; + std::unordered_map umap8; + std::multimap mmap; + std::unordered_multimap ummap; +}; + +TEST_F(FormatMap, EmptyMap) { + EXPECT_THAT(shaderc_util::format(empty_map, "pre", "in", "post"), IsEmpty()); + EXPECT_THAT(shaderc_util::format(empty_umap, "pre", "in", "post"), IsEmpty()); +} + +TEST_F(FormatMap, SingleEntry) { + EXPECT_EQ("PREoneIN1POST", shaderc_util::format(map1, "PRE", "IN", "POST")); + EXPECT_EQ("PREoneIN1POST", shaderc_util::format(umap1, "PRE", "IN", "POST")); +} + +TEST_F(FormatMap, EmptyPrefix) { + EXPECT_EQ("oneIN1POST", shaderc_util::format(map1, "", "IN", "POST")); + EXPECT_EQ("oneIN1POST", shaderc_util::format(umap1, "", "IN", "POST")); +} + +TEST_F(FormatMap, EmptyInfix) { + EXPECT_EQ("PREone1POST", shaderc_util::format(map1, "PRE", "", "POST")); + EXPECT_EQ("PREone1POST", shaderc_util::format(umap1, "PRE", "", "POST")); +} + +TEST_F(FormatMap, EmptyPostfix) { + EXPECT_EQ("PREoneIN1", shaderc_util::format(map1, "PRE", "IN", "")); + EXPECT_EQ("PREoneIN1", shaderc_util::format(umap1, "PRE", "IN", "")); +} + +TEST_F(FormatMap, LargerMap) { + const std::string result = shaderc_util::format(map8, "", "", "\n"), + uresult = shaderc_util::format(umap8, "", "", "\n"); + auto has_all = + AllOf(HasSubstr("1one\n"), HasSubstr("2two\n"), HasSubstr("3three\n"), + HasSubstr("4four\n"), HasSubstr("5five\n"), HasSubstr("6six\n"), + HasSubstr("7seven\n"), HasSubstr("8eight\n")); + EXPECT_THAT(result, has_all); + EXPECT_EQ(48u, result.size()); + EXPECT_THAT(uresult, has_all); + EXPECT_EQ(48u, uresult.size()); +} + +TEST_F(FormatMap, Multimap) { + const std::string result = shaderc_util::format(mmap, " ", "&", ""), + uresult = shaderc_util::format(ummap, " ", "&", ""); + auto has_all = AllOf(HasSubstr(" 1&100"), HasSubstr(" 1&200"), + HasSubstr(" 2&100"), HasSubstr(" 2&200")); + EXPECT_THAT(result, has_all); + EXPECT_EQ(4 * 6u, result.size()); + EXPECT_THAT(uresult, has_all); + EXPECT_EQ(4 * 6u, uresult.size()); +} + +} // anonymous namespace diff --git a/libshaderc_util/src/io.cc b/libshaderc_util/src/io.cc new file mode 100644 index 000000000..8af2aebe8 --- /dev/null +++ b/libshaderc_util/src/io.cc @@ -0,0 +1,100 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/io.h" + +#include "libshaderc_util/universal_unistd.h" + +#include +#include +#include +#include + +namespace { + +// Outputs a descriptive message for errno_value to cerr. +// This may be truncated to 1023 bytes on certain platforms. +void OutputFileErrorMessage(int errno_value) { +#ifdef _MSC_VER + // If the error message is more than 1023 bytes it will be truncated. + char buffer[1024]; + strerror_s(buffer, errno_value); + std::cerr << ": " << buffer << std::endl; +#else + std::cerr << ": " << strerror(errno_value) << std::endl; +#endif +} + +} // anonymous namespace + +namespace shaderc_util { + +bool ReadFile(const std::string& input_file_name, + std::vector* input_data) { + std::istream* stream = &std::cin; + std::ifstream input_file; + if (input_file_name != "-") { + input_file.open(input_file_name, std::ios_base::binary); + stream = &input_file; + if (input_file.fail()) { + std::cerr << "glslc: error: cannot open input file: '" << input_file_name + << "'"; + if (access(input_file_name.c_str(), R_OK) != 0) { + OutputFileErrorMessage(errno); + return false; + } + std::cerr << std::endl; + return false; + } + } + *input_data = std::vector((std::istreambuf_iterator(*stream)), + std::istreambuf_iterator()); + return true; +} + +bool WriteFile(const std::string& output_file_name, string_piece output_data) { + std::ostream* stream = &std::cout; + std::ofstream output_file; + if (output_file_name != "-") { + output_file.open(output_file_name, std::ios_base::binary); + stream = &output_file; + if (output_file.fail()) { + std::cerr << "glslc: error: cannot open output file: '" + << output_file_name << "'"; + if (access(output_file_name.c_str(), W_OK) != 0) { + OutputFileErrorMessage(errno); + return false; + } + std::cerr << std::endl; + return false; + } + } + if (output_data.size() > 0) { + stream->write(&output_data[0], output_data.size()); + if (!stream->good()) { + if (stream == &std::cout) { + std::cerr << "glslc: error: error writing to standard output" + << std::endl; + } else { + std::cerr << "glslc: error: error writing to output file: '" + << output_file_name << "'" << std::endl; + } + return false; + } + } + stream->flush(); + return true; +} + +} // namespace shaderc_util diff --git a/libshaderc_util/src/io_test.cc b/libshaderc_util/src/io_test.cc new file mode 100644 index 000000000..dccddd80e --- /dev/null +++ b/libshaderc_util/src/io_test.cc @@ -0,0 +1,60 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/io.h" + +#include + +namespace { + +using shaderc_util::ReadFile; +using shaderc_util::WriteFile; + +std::string ToString(const std::vector& v) { + return std::string(v.data(), v.size()); +} + +class ReadFileTest : public testing::Test { + protected: + // A vector to pass to ReadFile. + std::vector read_data; +}; + +TEST_F(ReadFileTest, CorrectContent) { + ASSERT_TRUE(ReadFile("include_file.1", &read_data)); + EXPECT_EQ("The quick brown fox jumps over a lazy dog.\n", + ToString(read_data)); +} + +TEST_F(ReadFileTest, EmptyContent) { + ASSERT_TRUE(ReadFile("dir/subdir/include_file.2", &read_data)); + EXPECT_TRUE(read_data.empty()); +} + +TEST_F(ReadFileTest, FileNotFound) { + EXPECT_FALSE(ReadFile("garbage garbage vjoiarhiupo hrfewi", &read_data)); +} + +TEST_F(ReadFileTest, EmptyFilename) { EXPECT_FALSE(ReadFile("", &read_data)); } + +TEST(WriteFileTest, Roundtrip) { + const std::string content = "random content 12345"; + const std::string filename = "WriteFileTestOutput.tmp"; + ASSERT_TRUE(WriteFile(filename, content)); + std::vector read_data; + ASSERT_TRUE(ReadFile(filename, &read_data)); + EXPECT_EQ(content, ToString(read_data)); +} + +} // anonymous namespace diff --git a/libshaderc_util/src/resources.cc b/libshaderc_util/src/resources.cc new file mode 100644 index 000000000..e7dc06930 --- /dev/null +++ b/libshaderc_util/src/resources.cc @@ -0,0 +1,129 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/resources.h" + +#include "glslang/Include/ResourceLimits.h" + +namespace shaderc_util { + +// These numbers come from the OpenGL 4.4 core profile specification Chapter 23 +// unless otherwise specified. +const TBuiltInResource kDefaultTBuiltInResource = { + /*.maxLights = */ 8, // From OpenGL 3.0 table 6.46. + /*.maxClipPlanes = */ 6, // From OpenGL 3.0 table 6.46. + /*.maxTextureUnits = */ 2, // From OpenGL 3.0 table 6.50. + /*.maxTextureCoords = */ 8, // From OpenGL 3.0 table 6.50. + /*.maxVertexAttribs = */ 16, + /*.maxVertexUniformComponents = */ 4096, + /*.maxVaryingFloats = */ 60, // From OpenGLES 3.1 table 6.44. + /*.maxVertexTextureImageUnits = */ 16, + /*.maxCombinedTextureImageUnits = */ 80, + /*.maxTextureImageUnits = */ 16, + /*.maxFragmentUniformComponents = */ 1024, + /*.maxDrawBuffers = */ 2, + /*.maxVertexUniformVectors = */ 256, + /*.maxVaryingVectors = */ 15, // From OpenGLES 3.1 table 6.44. + /*.maxFragmentUniformVectors = */ 256, + /*.maxVertexOutputVectors = */ 16, // maxVertexOutputComponents / 4 + /*.maxFragmentInputVectors = */ 15, // maxFragmentInputComponents / 4 + /*.minProgramTexelOffset = */ -8, + /*.maxProgramTexelOffset = */ 7, + /*.maxClipDistances = */ 8, + /*.maxComputeWorkGroupCountX = */ 65535, + /*.maxComputeWorkGroupCountY = */ 65535, + /*.maxComputeWorkGroupCountZ = */ 65535, + /*.maxComputeWorkGroupSizeX = */ 1024, + /*.maxComputeWorkGroupSizeX = */ 1024, + /*.maxComputeWorkGroupSizeZ = */ 64, + /*.maxComputeUniformComponents = */ 512, + /*.maxComputeTextureImageUnits = */ 16, + /*.maxComputeImageUniforms = */ 8, + /*.maxComputeAtomicCounters = */ 8, + /*.maxComputeAtomicCounterBuffers = */ 1, // From OpenGLES 3.1 Table 6.43 + /*.maxVaryingComponents = */ 60, + /*.maxVertexOutputComponents = */ 64, + /*.maxGeometryInputComponents = */ 64, + /*.maxGeometryOutputComponents = */ 128, + /*.maxFragmentInputComponents = */ 128, + /*.maxImageUnits = */ 8, // This does not seem to be defined anywhere, + // set to ImageUnits. + /*.maxCombinedImageUnitsAndFragmentOutputs = */ 8, + /*.maxCombinedShaderOutputResources = */ 8, + /*.maxImageSamples = */ 0, + /*.maxVertexImageUniforms = */ 0, + /*.maxTessControlImageUniforms = */ 0, + /*.maxTessEvaluationImageUniforms = */ 0, + /*.maxGeometryImageUniforms = */ 0, + /*.maxFragmentImageUniforms = */ 8, + /*.maxCombinedImageUniforms = */ 8, + /*.maxGeometryTextureImageUnits = */ 16, + /*.maxGeometryOutputVertices = */ 256, + /*.maxGeometryTotalOutputComponents = */ 1024, + /*.maxGeometryUniformComponents = */ 512, + /*.maxGeometryVaryingComponents = */ 60, // Does not seem to be defined + // anywhere, set equal to + // maxVaryingComponents. + /*.maxTessControlInputComponents = */ 128, + /*.maxTessControlOutputComponents = */ 128, + /*.maxTessControlTextureImageUnits = */ 16, + /*.maxTessControlUniformComponents = */ 1024, + /*.maxTessControlTotalOutputComponents = */ 4096, + /*.maxTessEvaluationInputComponents = */ 128, + /*.maxTessEvaluationOutputComponents = */ 128, + /*.maxTessEvaluationTextureImageUnits = */ 16, + /*.maxTessEvaluationUniformComponents = */ 1024, + /*.maxTessPatchComponents = */ 120, + /*.maxPatchVertices = */ 32, + /*.maxTessGenLevel = */ 64, + /*.maxViewports = */ 16, + /*.maxVertexAtomicCounters = */ 0, + /*.maxTessControlAtomicCounters = */ 0, + /*.maxTessEvaluationAtomicCounters = */ 0, + /*.maxGeometryAtomicCounters = */ 0, + /*.maxFragmentAtomicCounters = */ 8, + /*.maxCombinedAtomicCounters = */ 8, + /*.maxAtomicCounterBindings = */ 1, + /*.maxVertexAtomicCounterBuffers = */ 0, // From OpenGLES 3.1 Table 6.41. + + // ARB_shader_atomic_counters. + /*.maxTessControlAtomicCounterBuffers = */ 0, + /*.maxTessEvaluationAtomicCounterBuffers = */ 0, + /*.maxGeometryAtomicCounterBuffers = */ 0, + // /ARB_shader_atomic_counters. + + /*.maxFragmentAtomicCounterBuffers = */ 0, // From OpenGLES 3.1 Table 6.43. + /*.maxCombinedAtomicCounterBuffers = */ 1, + /*.maxAtomicCounterBufferSize = */ 32, + /*.maxTransformFeedbackBuffers = */ 4, + /*.maxTransformFeedbackInterleavedComponents = */ 64, + /*.maxCullDistances = */ 8, // ARB_cull_distance. + /*.maxCombinedClipAndCullDistances = */ 8, // ARB_cull_distance. + /*.maxSamples = */ 4, + // This is the glslang TLimits structure. + // It defines whether or not the following features are enabled. + // We want them to all be enabled. + /*.limits = */ { + /*.nonInductiveForLoops = */ 1, + /*.whileLoops = */ 1, + /*.doWhileLoops = */ 1, + /*.generalUniformIndexing = */ 1, + /*.generalAttributeMatrixVectorIndexing = */ 1, + /*.generalVaryingIndexing = */ 1, + /*.generalSamplerIndexing = */ 1, + /*.generalVariableIndexing = */ 1, + /*.generalConstantMatrixVectorIndexing = */ 1, + }}; + +} // namespace shaderc_util diff --git a/libshaderc_util/src/string_piece_test.cc b/libshaderc_util/src/string_piece_test.cc new file mode 100644 index 000000000..85be3ef66 --- /dev/null +++ b/libshaderc_util/src/string_piece_test.cc @@ -0,0 +1,384 @@ +// Copyright 2015 The Shaderc 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. + +#include "libshaderc_util/string_piece.h" + +#include +#include +#include + +namespace { + +using shaderc_util::string_piece; + +TEST(string_piece, creation) { + std::string my_string("std::string"); + const char* my_c_string = "c::string"; + + string_piece my_string_piece(my_string); + string_piece my_c_string_piece(my_c_string); + string_piece my_partial_c_string_piece(my_c_string, my_c_string + 3); + string_piece my_string_piece_string_piece(my_string_piece); + + EXPECT_EQ("std::string", my_string_piece); + EXPECT_EQ("c::string", my_c_string_piece); + EXPECT_EQ("c::", my_partial_c_string_piece); + EXPECT_EQ("std::string", my_string_piece_string_piece); +} + +TEST(string_piece, substr) { + string_piece my_string("my really long string"); + EXPECT_EQ("my really long string", my_string.substr(0, string_piece::npos)); + EXPECT_EQ("my really long string", my_string.substr(0)); + EXPECT_EQ("really long string", my_string.substr(3, string_piece::npos)); + EXPECT_EQ("really long string", my_string.substr(3)); + EXPECT_EQ("really", my_string.substr(3, 6)); +} + +TEST(string_piece, length) { + EXPECT_EQ(0u, string_piece().size()); + EXPECT_TRUE(string_piece().empty()); + EXPECT_EQ(10u, string_piece("0123456789").size()); + + std::string my_string("std::string"); + EXPECT_EQ(my_string.size(), string_piece(my_string).size()); +} + +TEST(string_piece, clear) { + string_piece my_string("my really long string"); + EXPECT_EQ("my really long string", my_string); + + string_piece other_string(my_string); + EXPECT_EQ("my really long string", other_string); + + my_string.clear(); + EXPECT_EQ("", my_string); + EXPECT_EQ("my really long string", other_string); +} + +TEST(string_piece, str) { + std::string test_string; + { + std::string temporary_string("my really long string"); + string_piece my_stringpiece(temporary_string); + string_piece my_substring = my_stringpiece.substr(3, 6); + + EXPECT_EQ("really", my_substring); + test_string = my_substring.str(); + } + EXPECT_EQ("really", test_string); +} + +template +bool find_char(char c) { + return c == C; +} + +TEST(string_piece, find_first_not_matching) { + string_piece my_string("aaaaaaa b"); + EXPECT_EQ(7u, my_string.find_first_not_matching(find_char<'a'>)); + EXPECT_EQ(0u, my_string.find_first_not_matching(find_char<'b'>)); + EXPECT_EQ(0u, string_piece(" ").find_first_not_matching(::isdigit)); + size_t npos = string_piece::npos; + EXPECT_EQ(npos, string_piece("").find_first_not_matching(::isdigit)); + EXPECT_EQ(npos, string_piece("123").find_first_not_matching(::isdigit)); + EXPECT_EQ(3u, string_piece("123 ").find_first_not_matching(::isdigit)); +} + +TEST(string_piece, find_first_not_of) { + size_t npos = string_piece::npos; + string_piece my_string("aaaaaaa b"); + EXPECT_EQ(7u, my_string.find_first_not_of("a")); + EXPECT_EQ(0u, my_string.find_first_not_of("b")); + EXPECT_EQ(7u, my_string.find_first_not_of('a')); + EXPECT_EQ(0u, my_string.find_first_not_of('b')); + EXPECT_EQ(0u, string_piece(" ").find_first_not_of("0123456789")); + + EXPECT_EQ(7u, my_string.find_first_not_of("a", 2)); + EXPECT_EQ(2u, my_string.find_first_not_of("b", 2)); + EXPECT_EQ(7u, my_string.find_first_not_of('a', 2)); + EXPECT_EQ(4u, my_string.find_first_not_of('b', 4)); + EXPECT_EQ(0u, string_piece(" ").find_first_not_of("0123456789")); + EXPECT_EQ(npos, string_piece(" ").find_first_not_of("0123456789", 5)); + + EXPECT_EQ(npos, string_piece("").find_first_not_of("012345689")); + EXPECT_EQ(npos, string_piece("").find_first_not_of("012345689", 1)); + EXPECT_EQ(npos, string_piece("123").find_first_not_of("0123456789")); + EXPECT_EQ(npos, string_piece("123").find_first_not_of("0123456789", 1)); + EXPECT_EQ(3u, string_piece("123 ").find_first_not_of("0123456789", 2)); + EXPECT_EQ(npos, string_piece("123 ").find_first_not_of("0123456789", 4)); + EXPECT_EQ(npos, string_piece("").find_first_not_of("1")); + EXPECT_EQ(npos, string_piece("111").find_first_not_of('1')); +} + +TEST(string_piece, find_first_of_char) { + const size_t npos = string_piece::npos; + string_piece my_string("my really long string"); + EXPECT_EQ(0u, my_string.find_first_of('m')); + EXPECT_EQ(3u, my_string.find_first_of('r')); + EXPECT_EQ(npos, my_string.find_first_of('z')); + + size_t pos = my_string.find_first_of('l'); + EXPECT_EQ(6u, pos); + // If pos points to a 'l' then we should just find that one + EXPECT_EQ(6u, my_string.find_first_of('l', pos)); + EXPECT_EQ(7u, my_string.find_first_of('l', pos + 1)); + EXPECT_EQ(10u, my_string.find_first_of('l', pos + 2)); + EXPECT_EQ(npos, my_string.find_first_of('l', pos + 5)); + EXPECT_EQ(npos, my_string.find_first_of('z', 0)); + EXPECT_EQ(npos, my_string.find_first_of('z', npos)); + + my_string.clear(); + EXPECT_EQ(npos, my_string.find_first_of('a')); + EXPECT_EQ(npos, my_string.find_first_of('a', 0)); +} + +TEST(string_piece, find_first_of) { + string_piece my_string("aaaaaa b"); + EXPECT_EQ(0u, my_string.find_first_of("a")); + EXPECT_EQ(7u, my_string.find_first_of("b")); + EXPECT_EQ(6u, my_string.find_first_of(" ")); + size_t npos = string_piece::npos; + EXPECT_EQ(npos, my_string.find_first_of("xh")); + EXPECT_EQ(6u, my_string.find_first_of(" x")); + EXPECT_EQ(6u, my_string.find_first_of(" b")); + EXPECT_EQ(0u, my_string.find_first_of("ab")); + + EXPECT_EQ(6u, my_string.find_first_of(" x", 2)); + EXPECT_EQ(6u, my_string.find_first_of(" b", 2)); + EXPECT_EQ(2u, my_string.find_first_of("ab", 2)); + EXPECT_EQ(npos, my_string.find_first_of("ab", 10)); + + EXPECT_EQ(npos, my_string.find_first_of("c")); + EXPECT_EQ(npos, my_string.find_first_of("c", 1)); + EXPECT_EQ(npos, string_piece(" ").find_first_of("a")); + EXPECT_EQ(npos, string_piece(" ").find_first_of("a", 10)); + EXPECT_EQ(npos, string_piece("aa").find_first_of("")); + EXPECT_EQ(npos, string_piece("aa").find_first_of("", 1)); + EXPECT_EQ(npos, string_piece("").find_first_of("")); + EXPECT_EQ(npos, string_piece("").find_first_of("", 1)); + EXPECT_EQ(npos, string_piece("").find_first_of("a")); + EXPECT_EQ(npos, string_piece("").find_first_of("ae")); + EXPECT_EQ(npos, string_piece("").find_first_of("ae", 32)); +} + +TEST(string_piece, find_last_of) { + string_piece my_string("aaaaaa b"); + EXPECT_EQ(5u, my_string.find_last_of('a')); + EXPECT_EQ(7u, my_string.find_last_of('b')); + EXPECT_EQ(6u, my_string.find_last_of(' ')); + EXPECT_EQ(5u, my_string.find_last_of("a")); + EXPECT_EQ(7u, my_string.find_last_of("b")); + EXPECT_EQ(6u, my_string.find_last_of(" ")); + size_t npos = string_piece::npos; + EXPECT_EQ(npos, my_string.find_last_of("xh")); + EXPECT_EQ(6u, my_string.find_last_of(" x")); + EXPECT_EQ(7u, my_string.find_last_of(" b")); + EXPECT_EQ(7u, my_string.find_last_of("ab")); + + EXPECT_EQ(4u, my_string.find_last_of('a', 4)); + EXPECT_EQ(5u, my_string.find_last_of('a', 6)); + EXPECT_EQ(0u, string_piece("abbbaa").find_last_of('a', 3)); + EXPECT_EQ(4u, string_piece("abbbaa").find_last_of('a', 4)); + EXPECT_EQ(5u, string_piece("abbbaa").find_last_of('a', 5)); + EXPECT_EQ(5u, string_piece("abbbaa").find_last_of('a', 6)); + EXPECT_EQ(npos, string_piece("abbbaa").find_last_of('c', 2)); + + EXPECT_EQ(npos, my_string.find_last_of("c")); + EXPECT_EQ(npos, string_piece(" ").find_last_of("a")); + EXPECT_EQ(npos, string_piece("aa").find_last_of("")); + EXPECT_EQ(npos, string_piece("").find_last_of("")); + EXPECT_EQ(npos, string_piece("").find_last_of("a")); + EXPECT_EQ(npos, my_string.find_last_of('c')); + EXPECT_EQ(npos, string_piece(" ").find_last_of('a')); + EXPECT_EQ(npos, string_piece("").find_last_of('a')); + EXPECT_EQ(npos, string_piece("").find_last_of("ae")); +} + +TEST(string_piece, begin_end) { + const char* my_string = "my really long string"; + string_piece p(my_string); + size_t pos = 0; + for (auto it = p.begin(); it != p.end(); ++it) { + EXPECT_EQ(my_string[pos++], *it); + } + pos = 0; + for (auto c : p) { + EXPECT_EQ(my_string[pos++], c); + } +} + +TEST(string_piece, starts_with) { + EXPECT_TRUE(string_piece("my string").starts_with("my")); + EXPECT_TRUE(string_piece("my string").starts_with("my s")); + EXPECT_TRUE(string_piece("my string").starts_with("m")); + EXPECT_TRUE(string_piece("my string").starts_with("")); + EXPECT_TRUE(string_piece("my string").starts_with("my string")); + EXPECT_TRUE(string_piece("").starts_with("")); + + EXPECT_FALSE(string_piece("").starts_with("a")); + EXPECT_FALSE(string_piece("my string").starts_with(" ")); + EXPECT_FALSE(string_piece("my string").starts_with("my stq")); + EXPECT_FALSE(string_piece("my string").starts_with("a")); + EXPECT_FALSE(string_piece("my string").starts_with("my strings")); +} + +TEST(string_piece, find) { + const size_t npos = string_piece::npos; + string_piece my_string("gooogle gooogle"); + + EXPECT_EQ(0u, my_string.find("")); + + EXPECT_EQ(0u, my_string.find("g")); + EXPECT_EQ(4u, my_string.find("g", 1)); + + EXPECT_EQ(0u, my_string.find("go")); + EXPECT_EQ(8u, my_string.find("go", 1)); + + EXPECT_EQ(1u, my_string.find("oo")); + EXPECT_EQ(1u, my_string.find("oo", 1)); + EXPECT_EQ(2u, my_string.find("oo", 2)); + EXPECT_EQ(9u, my_string.find("oo", 3)); + + EXPECT_EQ(4u, my_string.find("gle")); + EXPECT_EQ(12u, my_string.find("gle", 5)); + + EXPECT_EQ(npos, my_string.find("0")); + EXPECT_EQ(npos, my_string.find("does-not-exist")); + EXPECT_EQ(npos, my_string.find("longer than gooogle gooogle")); + + EXPECT_EQ(npos, my_string.find("", npos)); + EXPECT_EQ(npos, my_string.find("gle", npos)); +} + +TEST(string_piece, get_fields) { + string_piece input; + std::vector expected_lines; + EXPECT_EQ(expected_lines, input.get_fields('\n')); + EXPECT_EQ(expected_lines, input.get_fields('\n', true)); + + input = "first line"; + expected_lines = {"first line"}; + EXPECT_EQ(expected_lines, input.get_fields('\n')); + EXPECT_EQ(expected_lines, input.get_fields('\n', true)); + + input = "first line\n"; + expected_lines = {"first line"}; + EXPECT_EQ(expected_lines, input.get_fields('\n')); + expected_lines = {"first line\n"}; + EXPECT_EQ(expected_lines, input.get_fields('\n', true)); + + input = "\nfirst line"; + expected_lines = {"", "first line"}; + EXPECT_EQ(expected_lines, input.get_fields('\n')); + expected_lines = {"\n", "first line"}; + EXPECT_EQ(expected_lines, input.get_fields('\n', true)); + + input = "first line\nsecond line\nthird line\n"; + expected_lines = {"first line", "second line", "third line"}; + EXPECT_EQ(expected_lines, input.get_fields('\n')); + expected_lines = {"first line\n", "second line\n", "third line\n"}; + EXPECT_EQ(expected_lines, input.get_fields('\n', true)); + + input = "first line\n\nsecond line\n\nthird line\n\n"; + expected_lines = {"first line", "", "second line", "", "third line", ""}; + EXPECT_EQ(expected_lines, input.get_fields('\n')); + expected_lines = {"first line\n", "\n", "second line\n", + "\n", "third line\n", "\n"}; + EXPECT_EQ(expected_lines, input.get_fields('\n', true)); +} + +TEST(string_piece, operator_stream_out) { + std::stringstream stream; + string_piece my_string("my really long string"); + stream << my_string; + EXPECT_EQ("my really long string", stream.str()); + stream.str(""); + stream << my_string.substr(3, 6); + EXPECT_EQ("really", stream.str()); + stream.str(""); + stream << string_piece(); + EXPECT_EQ("", stream.str()); +} + +TEST(string_piece, lrstrip) { + string_piece nothing_to_remove("abcdefg"); + EXPECT_EQ("abcdefg", nothing_to_remove.lstrip("hijklmn")); + EXPECT_EQ("abcdefg", nothing_to_remove.rstrip("hijklmn")); + EXPECT_EQ("abcdefg", nothing_to_remove.strip("hijklmn")); + + string_piece empty_string(""); + EXPECT_EQ(0u, empty_string.lstrip("google").size()); + EXPECT_EQ(0u, empty_string.rstrip("google").size()); + EXPECT_EQ(0u, empty_string.strip("google").size()); + + string_piece remove_nothing("asdfghjkl"); + EXPECT_EQ("asdfghjkl", remove_nothing.lstrip("")); + EXPECT_EQ("asdfghjkl", remove_nothing.rstrip("")); + EXPECT_EQ("asdfghjkl", remove_nothing.strip("")); + + string_piece strip_numbers("0123g4o5o6g7l8e9"); + EXPECT_EQ("g4o5o6g7l8e9", strip_numbers.lstrip("0123456789")); + EXPECT_EQ("0123g4o5o6g7l8e", strip_numbers.rstrip("0123456789")); + EXPECT_EQ("g4o5o6g7l8e", strip_numbers.strip("0123456789")); +} + +TEST(string_piece, strip_whitespace) { + string_piece lots_of_space(" b i n g o "); + EXPECT_EQ("b i n g o", lots_of_space.strip_whitespace()); + + string_piece whitespaces("\v\t\f\n\rleft\r\t\f\n\vright\f\n\t\v\r"); + EXPECT_EQ("left\r\t\f\n\vright", whitespaces.strip_whitespace()); + + string_piece remove_all(" \t "); + EXPECT_EQ(0u, remove_all.strip_whitespace().size()); +} + +TEST(string_piece, not_equal) { + EXPECT_FALSE(string_piece() != string_piece()); + EXPECT_FALSE(string_piece("") != string_piece()); + EXPECT_TRUE(string_piece() != string_piece(" ")); + EXPECT_FALSE(string_piece("abc") != string_piece("abc")); + EXPECT_TRUE(string_piece("abc") != string_piece("abc ")); + EXPECT_TRUE(string_piece("abc") != string_piece("abd")); + + EXPECT_FALSE("" != string_piece()); + EXPECT_FALSE("" != string_piece("")); + EXPECT_TRUE(" " != string_piece("")); + EXPECT_FALSE("abc" != string_piece("abc")); + EXPECT_TRUE(" abc" != string_piece("abc")); + EXPECT_TRUE("abd" != string_piece("abc")); +} + +TEST(string_piece, data) { + EXPECT_EQ(nullptr, string_piece().data()); + const char* empty = ""; + EXPECT_EQ(empty, string_piece(empty).data()); + const char* space = " "; + EXPECT_EQ(space, string_piece(space).data()); + const char* a = "a"; + EXPECT_EQ(a, string_piece(a).data()); + const char* abc = "abc"; + EXPECT_EQ(abc, string_piece(abc).data()); + EXPECT_EQ(abc + 1, string_piece(abc).substr(1).data()); + EXPECT_EQ(abc + 3, string_piece(abc).substr(3).data()); +} + +TEST(string_piece, unordered_map) { + std::unordered_map dict; + dict["abc"] = 123; + EXPECT_EQ(123, dict["abc"]); +} + +} // anonymous namespace diff --git a/libshaderc_util/testdata/copy-to-build.cmake b/libshaderc_util/testdata/copy-to-build.cmake new file mode 100644 index 000000000..bf50a72fc --- /dev/null +++ b/libshaderc_util/testdata/copy-to-build.cmake @@ -0,0 +1,2 @@ +file(GLOB all_files ${CMAKE_CURRENT_LIST_DIR}/*) +file(COPY ${all_files} DESTINATION .) \ No newline at end of file diff --git a/libshaderc_util/testdata/dir/subdir/include_file.2 b/libshaderc_util/testdata/dir/subdir/include_file.2 new file mode 100644 index 000000000..e69de29bb diff --git a/libshaderc_util/testdata/include_file.1 b/libshaderc_util/testdata/include_file.1 new file mode 100644 index 000000000..de656e386 --- /dev/null +++ b/libshaderc_util/testdata/include_file.1 @@ -0,0 +1 @@ +The quick brown fox jumps over a lazy dog. diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt new file mode 100644 index 000000000..787dfb7c4 --- /dev/null +++ b/third_party/CMakeLists.txt @@ -0,0 +1,49 @@ +add_subdirectory(gmock-1.7.0) + +set(OLD_PLATFORM_TOOLSET ${CMAKE_GENERATOR_TOOLSET}) +# Configure third party projects. +add_subdirectory(glslang) +if(WIN32) + # This is unfortunate but glslang forces our + # platform toolset to be v110, which we may not even have + # installed, undo anything glslang has done to it. + set(CMAKE_GENERATOR_TOOLSET "${OLD_PLATFORM_TOOLSET}" CACHE STRING + "Platform Toolset" FORCE) +endif() +# Configure out-of-source-directory tests for glslang. +# The glslang project uses a bash script called "runtests" to run tests. +# The runtests script assumes the glslangValidator executable exists in +# a location inside the source tree, but we build it elsewhere. +# We need to copy the test files, fix the path references, and then run tests. + +set(GLSLANG_TEST_SRC_DIR ${CMAKE_CURRENT_SOURCE_DIR}/glslang/Test) +set(GLSLANG_TEST_BIN_DIR + ${CMAKE_CURRENT_BINARY_DIR}/test-glslang/${CMAKE_CFG_INTDIR}) + +# If we are building in a multi-configuration setting we have +# to put the glslang tests into their respective subdirectories. +if (CMAKE_CONFIGURATION_TYPES) + set(GLSLANG_CONFIGURATION_DIR ${CMAKE_CFG_INTDIR}) +endif() + +find_program(PYTHON python REQUIRED) + +add_custom_target(copy-tests-if-necessary ALL + COMMAND ${PYTHON} ${shaderc_SOURCE_DIR}/utils/copy-tests-if-necessary.py + ${GLSLANG_TEST_SRC_DIR} ${GLSLANG_TEST_BIN_DIR} ${GLSLANG_CONFIGURATION_DIR} + COMMENT "Copying and patching glslang tests if needed") + +if (CMAKE_CONFIGURATION_TYPES) + # If we are running a multi-configuration project, + # the tests will be in test-glslang/${Configuration} + add_test(NAME glslang-testsuite + COMMAND bash runtests + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test-glslang/$ + ) +else() + add_test(NAME glslang-testsuite + COMMAND bash runtests + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/test-glslang/ + ) +endif() + diff --git a/utils/copy-tests-if-necessary.py b/utils/copy-tests-if-necessary.py new file mode 100755 index 000000000..575b03bc6 --- /dev/null +++ b/utils/copy-tests-if-necessary.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python +# Copyright 2015 The Shaderc 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. + +"""Copies tests from the source directory to the binary directory if something +in the source directory has changed. It also updates the path in runtests +to point to the correct binary directory. + +Arguments: glslang_test_source_dir glslang_test_bin_dir [intermediate-dir] + +intermediate-dir is optional, it specifies that there the additional directory +between the root, and the tests/binary. +""" + +import os +import shutil +import sys + + +def get_modified_times(path): + """Returns a string containing a newline-separated set of + filename:last_modified_time pairs for all files rooted at path. + """ + output = [] + for root, dirnames, filenames in os.walk(path): + for filename in filenames: + fullpath = os.path.join(root, filename) + output.append( + filename + ":" + + str(os.path.getmtime(os.path.join(root, filename))) + "\n") + return "".join(output) + + +def read_file(path): + """Reads a file and returns the data as a string.""" + output = "" + try: + # If we could not open then we simply return "" as the output + with open(path, "r") as content: + output = content.read() + except: + pass + return output + + +def write_file(path, output): + """Writes an output string to the file located at path.""" + with open(path, "w") as content: + content.write(output) + + +def substitute_file(path, substitution): + """Substitutes all instances of substitution[0] with substitution[1] for the + file located at path.""" + with open(path, "r") as content: + f_input = content.read() + if f_input: + f_input = f_input.replace(substitution[0], substitution[1]) + with open(path, "w") as content: + content.write(f_input) + + +def substitute_files(path, substitution): + """Runs substitute_file() on all files rooted at path.""" + f_input = "" + for root, dirnames, filenames in os.walk(path): + for filename in filenames: + substitute_file(os.path.join(root, filename), substitution) + + +def setup_directory(source, dest): + """Removes the destination directory if it exists and copies the source + directory over the destination if it exists. + """ + try: + shutil.rmtree(dest) + except: + # shutil will throw if it could not find the directory. + pass + shutil.copytree(source, dest) + + +def main(): + glsl_src_dir = os.path.normpath(sys.argv[1]) + glsl_bin_dir = os.path.normpath(sys.argv[2]) + intermediate_directory = None + if (len(sys.argv) > 3): + intermediate_directory = sys.argv[3] + glsl_list_file = os.path.join(glsl_bin_dir, "glsl_test_list") + + src_glsl_stamp = get_modified_times(glsl_src_dir) + old_glsl_stamp = read_file(glsl_list_file) + + target_location = "../glslang/StandAlone/" + if intermediate_directory: + target_location = "../" + target_location + intermediate_directory + "/" + target_location = "EXE=" + target_location + if src_glsl_stamp != old_glsl_stamp: + setup_directory(glsl_src_dir, glsl_bin_dir) + substitute_file(os.path.join(glsl_bin_dir, "runtests"), + ("EXE=../build/install/bin/", target_location)) + write_file(glsl_list_file, src_glsl_stamp) + + +if __name__ == "__main__": + main() diff --git a/utils/remove-file-by-suffix.py b/utils/remove-file-by-suffix.py new file mode 100755 index 000000000..39af161c6 --- /dev/null +++ b/utils/remove-file-by-suffix.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +# Copyright 2015 The Shaderc 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. + +# Removes all files with a certain suffix in a given path recursively. + +# Arguments: path suffix + +import os +import sys + + +def main(): + path = sys.argv[1] + suffix = sys.argv[2] + for root, _, filenames in os.walk(path): + for filename in filenames: + if filename.endswith(suffix): + os.remove(os.path.join(root, filename)) + + +if __name__ == '__main__': + main()