diff --git a/CMakeLists.txt b/CMakeLists.txt index c8e31826..4ebdb21b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,9 @@ set_property(GLOBAL PROPERTY USE_FOLDERS ON) # Adhere to GNU filesystem layout conventions include(GNUInstallDirs) +# Needed for CMAKE_DEPENDENT_OPTION macro +include(CMakeDependentOption) + option(BUILD_SHARED_LIBS "Build Shared Libraries" OFF) set(LIB_TYPE STATIC) @@ -28,7 +31,10 @@ option(ENABLE_GLSLANG_BINARIES "Builds glslangValidator and spirv-remap" ON) option(ENABLE_NV_EXTENSIONS "Enables support of Nvidia-specific extensions" ON) -option(ENABLE_HLSL "Enables HLSL input support" ON) +option(ENABLE_GLSLANG_WEB "Reduces glslang to minumum needed for web use" OFF) +option(ENABLE_EMSCRIPTEN_SINGLE_FILE "If using emscripten, enables SINGLE_FILE build" OFF) + +CMAKE_DEPENDENT_OPTION(ENABLE_HLSL "Enables HLSL input support" ON "NOT ENABLE_GLSLANG_WEB" OFF) option(ENABLE_OPT "Enables spirv-opt capability if present" ON) @@ -98,6 +104,32 @@ elseif(${CMAKE_CXX_COMPILER_ID} MATCHES "MSVC") add_compile_options(/GR-) # Disable RTTI endif() +if(ENABLE_GLSLANG_WEB) + if(EMSCRIPTEN) + add_compile_options(-Os -fno-exceptions) + add_compile_options("SHELL: -s WASM=1") + add_compile_options("SHELL: -s WASM_OBJECT_FILES=0") + add_link_options(-Os) + add_link_options("SHELL: -s FILESYSTEM=0") + add_link_options("SHELL: --llvm-lto 1") + add_link_options("SHELL: --closure 1") + add_link_options("SHELL: -s ENVIRONMENT=web,worker") + add_link_options("SHELL: -s ALLOW_MEMORY_GROWTH=1") + + add_link_options("SHELL: -s MODULARIZE=1") + if(ENABLE_EMSCRIPTEN_SINGLE_FILE) + add_link_options("SHELL: -s SINGLE_FILE=1") + endif(ENABLE_EMSCRIPTEN_SINGLE_FILE) + else() + if(MSVC) + add_compile_options(/Os /GR-) + else() + add_compile_options(-Os -fno-exceptions) + add_link_options(-Os) + endif() + endif(EMSCRIPTEN) +endif(ENABLE_GLSLANG_WEB) + # Request C++11 if(${CMAKE_VERSION} VERSION_LESS 3.1) # CMake versions before 3.1 do not understand CMAKE_CXX_STANDARD diff --git a/README.md b/README.md index ff89e4c0..334b086a 100755 --- a/README.md +++ b/README.md @@ -163,6 +163,22 @@ bison --defines=MachineIndependent/glslang_tab.cpp.h The above command is also available in the bash script at `glslang/updateGrammar`. +### WASM for the the Web + +Use the steps in [Build Steps](#build-steps), which following notes/exceptions: +* `emsdk` needs to be present in your executable search path, *PATH* for + Bash-like enivironments + + Instructions located + [here](https://emscripten.org/docs/getting_started/downloads.html#sdk-download-and-install) +* Do not checkout SPIRV-Tools into `External` + + Does not work correctly with emscripten out of the box and we don't want it + in the build anyway. *TBD* Have build ignore SPIRV-Tools for web build +* Wrap call to `cmake` using `emconfigure` with ENABLE_GLSLANG_WEB=ON: + + e.g. For Linux, `emconfigure cmake -DCMAKE_BUILD_TYPE=Release + -DENABLE_GLSLANG_WEB=ON -DCMAKE_INSTALL_PREFIX="$(pwd)/install" ..` +* To get a 'true' minimized build, make sure to use `brotli` to compress the .js + and .wasm files + Testing ------- diff --git a/glslang/CMakeLists.txt b/glslang/CMakeLists.txt index 60169fb7..4c5cfedb 100644 --- a/glslang/CMakeLists.txt +++ b/glslang/CMakeLists.txt @@ -122,3 +122,15 @@ if(ENABLE_GLSLANG_INSTALL) install(FILES ${file} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/glslang/${dir}) endforeach() endif(ENABLE_GLSLANG_INSTALL) + +if(ENABLE_GLSLANG_WEB) + add_executable(glslang.js glslang.js.cpp) + glslang_set_link_args(glslang.js) + target_link_libraries(glslang.js glslang SPIRV) + if(EMSCRIPTEN) + set_target_properties(glslang.js PROPERTIES + OUTPUT_NAME "glslang" + SUFFIX ".js" + LINK_FLAGS "--bind") + endif(EMSCRIPTEN) +endif(ENABLE_GLSLANG_WEB) diff --git a/glslang/glslang.js.cpp b/glslang/glslang.js.cpp new file mode 100644 index 00000000..4a585545 --- /dev/null +++ b/glslang/glslang.js.cpp @@ -0,0 +1,286 @@ +// +// Copyright (C) 2019 Google, Inc. +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// +// Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following +// disclaimer in the documentation and/or other materials provided +// with the distribution. +// +// Neither the name of 3Dlabs Inc. Ltd. nor the names of its +// contributors may be used to endorse or promote products derived +// from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. +// + +#include +#ifdef __EMSCRIPTEN__ +#include +#endif // __EMSCRIPTEN__ +#include + +#include "../SPIRV/GlslangToSpv.h" +#include "../SPIRV/doc.h" +#include "./../glslang/Public/ShaderLang.h" + +const TBuiltInResource DefaultTBuiltInResource = { + /* .MaxLights = */ 32, + /* .MaxClipPlanes = */ 6, + /* .MaxTextureUnits = */ 32, + /* .MaxTextureCoords = */ 32, + /* .MaxVertexAttribs = */ 64, + /* .MaxVertexUniformComponents = */ 4096, + /* .MaxVaryingFloats = */ 64, + /* .MaxVertexTextureImageUnits = */ 32, + /* .MaxCombinedTextureImageUnits = */ 80, + /* .MaxTextureImageUnits = */ 32, + /* .MaxFragmentUniformComponents = */ 4096, + /* .MaxDrawBuffers = */ 32, + /* .MaxVertexUniformVectors = */ 128, + /* .MaxVaryingVectors = */ 8, + /* .MaxFragmentUniformVectors = */ 16, + /* .MaxVertexOutputVectors = */ 16, + /* .MaxFragmentInputVectors = */ 15, + /* .MinProgramTexelOffset = */ -8, + /* .MaxProgramTexelOffset = */ 7, + /* .MaxClipDistances = */ 8, + /* .MaxComputeWorkGroupCountX = */ 65535, + /* .MaxComputeWorkGroupCountY = */ 65535, + /* .MaxComputeWorkGroupCountZ = */ 65535, + /* .MaxComputeWorkGroupSizeX = */ 1024, + /* .MaxComputeWorkGroupSizeY = */ 1024, + /* .MaxComputeWorkGroupSizeZ = */ 64, + /* .MaxComputeUniformComponents = */ 1024, + /* .MaxComputeTextureImageUnits = */ 16, + /* .MaxComputeImageUniforms = */ 8, + /* .MaxComputeAtomicCounters = */ 8, + /* .MaxComputeAtomicCounterBuffers = */ 1, + /* .MaxVaryingComponents = */ 60, + /* .MaxVertexOutputComponents = */ 64, + /* .MaxGeometryInputComponents = */ 64, + /* .MaxGeometryOutputComponents = */ 128, + /* .MaxFragmentInputComponents = */ 128, + /* .MaxImageUnits = */ 8, + /* .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 = */ 1024, + /* .MaxGeometryVaryingComponents = */ 64, + /* .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, + /* .MaxTessControlAtomicCounterBuffers = */ 0, + /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, + /* .MaxGeometryAtomicCounterBuffers = */ 0, + /* .MaxFragmentAtomicCounterBuffers = */ 1, + /* .MaxCombinedAtomicCounterBuffers = */ 1, + /* .MaxAtomicCounterBufferSize = */ 16384, + /* .MaxTransformFeedbackBuffers = */ 4, + /* .MaxTransformFeedbackInterleavedComponents = */ 64, + /* .MaxCullDistances = */ 8, + /* .MaxCombinedClipAndCullDistances = */ 8, + /* .MaxSamples = */ 4, + /* .maxMeshOutputVerticesNV = */ 256, + /* .maxMeshOutputPrimitivesNV = */ 512, + /* .maxMeshWorkGroupSizeX_NV = */ 32, + /* .maxMeshWorkGroupSizeY_NV = */ 1, + /* .maxMeshWorkGroupSizeZ_NV = */ 1, + /* .maxTaskWorkGroupSizeX_NV = */ 32, + /* .maxTaskWorkGroupSizeY_NV = */ 1, + /* .maxTaskWorkGroupSizeZ_NV = */ 1, + /* .maxMeshViewCountNV = */ 4, + + /* .limits = */ { + /* .nonInductiveForLoops = */ 1, + /* .whileLoops = */ 1, + /* .doWhileLoops = */ 1, + /* .generalUniformIndexing = */ 1, + /* .generalAttributeMatrixVectorIndexing = */ 1, + /* .generalVaryingIndexing = */ 1, + /* .generalSamplerIndexing = */ 1, + /* .generalVariableIndexing = */ 1, + /* .generalConstantMatrixVectorIndexing = */ 1, + }}; + +/* + * Takes in a GLSL shader as a string and converts it to SPIR-V in binary form. + * + * |glsl| Char array created using create_input_buffer and populated + * with the shader to be converted. + * This buffer must be destroyed using destroy_input_buffer. + * |shader_type| Magic number indicating the type of shader being processed. + * Legal values are as follows: + * Vertex = 0 + * Geometry = 3 + * Fragment = 4 + * |spirv| Pointer to an output buffer that will be updated with the + * resulting SPIR-V shader. + * This buffer must be destroyed using destroy_output_buffer. + * + * |spirv_len| Length of the output binary buffer. + * |gen_debug| Flag to indicate if debug information should be generated. + * + * Return 0 on success, non-0 on failure. + */ +#ifdef __EMSCRIPTEN__ +EMSCRIPTEN_KEEPALIVE +#endif // __EMSCRIPTEN__ +int convert_glsl_to_spirv(const char* glsl, int shader_type, unsigned int** spirv, size_t* spirv_len, bool gen_debug) +{ + int ret_val = 0; + if (glsl == nullptr || spirv == nullptr) { + return 1; + } + *spirv = nullptr; + + if (shader_type != 0 && shader_type != 3 && shader_type != 4) { + return 2; + } + + EShLanguage shader_lang = static_cast(shader_type); + + glslang::InitializeProcess(); + { + glslang::TShader shader(shader_lang); + shader.setStrings(&glsl, 1); + shader.setEnvInput(glslang::EShSourceGlsl, shader_lang, glslang::EShClientOpenGL, 100); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); + shader.parse(&DefaultTBuiltInResource, 100, true, EShMsgDefault); + + glslang::TProgram program; + program.addShader(&shader); + program.link(EShMsgDefault); + + std::vector output; + std::string warningsErrors; + glslang::SpvOptions spvOptions; + spvOptions.generateDebugInfo = gen_debug; + spvOptions.disableOptimizer = false; + spvOptions.optimizeSize = false; + spvOptions.disassemble = false; + spvOptions.validate = false; + + glslang::GlslangToSpv(*program.getIntermediate(EShLangFragment), output, nullptr, &spvOptions); + + *spirv_len = output.size(); + *spirv = static_cast(malloc(*spirv_len * sizeof(unsigned int))); + if (*spirv != nullptr) { + memcpy(*spirv, output.data(), *spirv_len); + } else { + ret_val = 3; + } + } + glslang::FinalizeProcess(); + return ret_val; +} + +/* + * Created a valid input buffer. + * + * Must be destroyed later using destroy_input_buffer. + */ +#ifdef __EMSCRIPTEN__ +EMSCRIPTEN_KEEPALIVE +#endif // __EMSCRIPTEN__ +char* create_input_buffer(int count) { return static_cast(malloc(count * sizeof(char))); } + +/* + * Destroys a buffer created by create_input_buffer + */ +#ifdef __EMSCRIPTEN__ +EMSCRIPTEN_KEEPALIVE +#endif // __EMSCRIPTEN__ +void destroy_input_buffer(char* p) +{ + if (p != nullptr) + free(p); +} + +/* + * Destroys a buffer created by convert_glsl_to_spirv + */ +#ifdef __EMSCRIPTEN__ +EMSCRIPTEN_KEEPALIVE +#endif // __EMSCRIPTEN__ +void destroy_ouput_buffer(unsigned int* p) +{ + if (p != nullptr) + free(p); +} + + +/* + * For non-Emscripten builds we supply a generic main, so that the glslang.js + * build target can generate an executable with a trivial use case instead of + * generating a WASM binary. This is done so that there is a target that can be + * built and output analyzed using desktop tools, since WASM binaries are + * specific to the Emscripten toolchain. + */ +#ifndef __EMSCRIPTEN__ +int main() { + const char* input_text = R"(#version 310 es + +void main() { })"; + + char* input; + unsigned int* output; + size_t output_len; + + input = create_input_buffer(sizeof(input_text)); + assert(input != nullptr); + memcpy(input, input_text, sizeof(input_text)); + + convert_glsl_to_spirv(input, 4, &output, &output_len, false); + destroy_ouput_buffer(output); + destroy_input_buffer(input); + return 0; +} +#endif // !__EMSCRIPTEN__