diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ebdb21b..6c84ae84 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ option(ENABLE_NV_EXTENSIONS "Enables support of Nvidia-specific extensions" 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) +option(ENABLE_EMSCRIPTEN_ENVIRONMENT_NODE "If using emscripten, builds to run on Node instead of Web" OFF) CMAKE_DEPENDENT_OPTION(ENABLE_HLSL "Enables HLSL input support" ON "NOT ENABLE_GLSLANG_WEB" OFF) @@ -113,13 +114,20 @@ if(ENABLE_GLSLANG_WEB) 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) + + if(ENABLE_EMSCRIPTEN_ENVIRONMENT_NODE) + add_link_options("SHELL: -s ENVIRONMENT=node") + add_link_options("SHELL: -s BINARYEN_ASYNC_COMPILATION=0") + else() + add_link_options("SHELL: -s ENVIRONMENT=web,worker") + add_link_options("SHELL: -s EXPORT_ES6=1") + endif() else() if(MSVC) add_compile_options(/Os /GR-) diff --git a/glslang/CMakeLists.txt b/glslang/CMakeLists.txt index 4c5cfedb..84b8765a 100644 --- a/glslang/CMakeLists.txt +++ b/glslang/CMakeLists.txt @@ -131,6 +131,7 @@ if(ENABLE_GLSLANG_WEB) set_target_properties(glslang.js PROPERTIES OUTPUT_NAME "glslang" SUFFIX ".js" - LINK_FLAGS "--bind") + LINK_FLAGS "--bind -s EXPORT_NAME=\"glslangModule\"") + em_link_pre_js(glslang.js ${CMAKE_CURRENT_SOURCE_DIR}/glslang.pre.js) endif(EMSCRIPTEN) endif(ENABLE_GLSLANG_WEB) diff --git a/glslang/glslang.js.cpp b/glslang/glslang.js.cpp index 4a585545..45b3d3f6 100644 --- a/glslang/glslang.js.cpp +++ b/glslang/glslang.js.cpp @@ -34,6 +34,8 @@ // #include +#include + #ifdef __EMSCRIPTEN__ #include #endif // __EMSCRIPTEN__ @@ -43,6 +45,10 @@ #include "../SPIRV/doc.h" #include "./../glslang/Public/ShaderLang.h" +#ifndef EMSCRIPTEN_KEEPALIVE +#define EMSCRIPTEN_KEEPALIVE +#endif + const TBuiltInResource DefaultTBuiltInResource = { /* .MaxLights = */ 32, /* .MaxClipPlanes = */ 6, @@ -149,113 +155,95 @@ const TBuiltInResource DefaultTBuiltInResource = { /* .generalConstantMatrixVectorIndexing = */ 1, }}; +static bool initialized = false; + +extern "C" { + /* * 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: + * |glsl| Null-terminated string containing the shader to be converted. + * |stage_int| 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. + * Compute = 5 * |gen_debug| Flag to indicate if debug information should be generated. + * |spirv| Output parameter for a pointer to the resulting SPIR-V data. + * |spirv_len| Output parameter for the length of the output binary buffer. * - * Return 0 on success, non-0 on failure. + * Returns a void* pointer which, if not null, must be destroyed by + * destroy_output_buffer.o. (This is not the same pointer returned in |spirv|.) + * If null, the compilation failed. */ -#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) +void* convert_glsl_to_spirv(const char* glsl, int stage_int, bool gen_debug, uint32_t** spirv, size_t* spirv_len) { - int ret_val = 0; - if (glsl == nullptr || spirv == nullptr) { - return 1; + if (glsl == nullptr) { + fprintf(stderr, "Input pointer null\n"); + return nullptr; + } + if (spirv == nullptr || spirv_len == nullptr) { + fprintf(stderr, "Output pointer null\n"); + return nullptr; } *spirv = nullptr; + *spirv_len = 0; - if (shader_type != 0 && shader_type != 3 && shader_type != 4) { - return 2; + if (stage_int != 0 && stage_int != 4 && stage_int != 5) { + fprintf(stderr, "Invalid shader stage\n"); + return nullptr; + } + EShLanguage stage = static_cast(stage_int); + + if (!initialized) { + glslang::InitializeProcess(); + initialized = true; } - 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::TShader shader(stage); + shader.setStrings(&glsl, 1); + shader.setEnvInput(glslang::EShSourceGlsl, stage, glslang::EShClientVulkan, 100); + shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_1); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_3); + if (!shader.parse(&DefaultTBuiltInResource, 100, true, EShMsgDefault)) { + fprintf(stderr, "Parse failed\n"); + fprintf(stderr, "%s\n", shader.getInfoLog()); + return nullptr; } - 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))); } + glslang::TProgram program; + program.addShader(&shader); + if (!program.link(EShMsgDefault)) { + fprintf(stderr, "Link failed\n"); + fprintf(stderr, "%s\n", program.getInfoLog()); + return nullptr; + } -/* - * 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); + glslang::SpvOptions spvOptions; + spvOptions.generateDebugInfo = gen_debug; + spvOptions.optimizeSize = false; + spvOptions.disassemble = false; + spvOptions.validate = false; + + std::vector* output = new std::vector; + glslang::GlslangToSpv(*program.getIntermediate(stage), *output, nullptr, &spvOptions); + + *spirv_len = output->size(); + *spirv = output->data(); + return output; } /* * Destroys a buffer created by convert_glsl_to_spirv */ -#ifdef __EMSCRIPTEN__ EMSCRIPTEN_KEEPALIVE -#endif // __EMSCRIPTEN__ -void destroy_ouput_buffer(unsigned int* p) +void destroy_output_buffer(void* p) { - if (p != nullptr) - free(p); + delete static_cast*>(p); } +} // extern "C" /* * For non-Emscripten builds we supply a generic main, so that the glslang.js @@ -266,21 +254,17 @@ void destroy_ouput_buffer(unsigned int* p) */ #ifndef __EMSCRIPTEN__ int main() { - const char* input_text = R"(#version 310 es + const char* input = R"(#version 310 es void main() { })"; - char* input; - unsigned int* output; + uint32_t* 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); + void* id = convert_glsl_to_spirv(input, 4, false, &output, &output_len); + assert(output != nullptr); + assert(output_len != 0); + destroy_output_buffer(id); return 0; } -#endif // !__EMSCRIPTEN__ +#endif // ifndef __EMSCRIPTEN__ diff --git a/glslang/glslang.pre.js b/glslang/glslang.pre.js new file mode 100644 index 00000000..dd7100b3 --- /dev/null +++ b/glslang/glslang.pre.js @@ -0,0 +1,45 @@ +Module['compileGLSLZeroCopy'] = function(glsl, shader_stage, gen_debug) { + gen_debug = !!gen_debug; + + var shader_stage_int; + if (shader_stage === 'vertex') { + shader_stage_int = 0; + } else if (shader_stage === 'fragment') { + shader_stage_int = 4; + } else if (shader_stage === 'compute') { + shader_stage_int = 5; + } else { + throw new Error("shader_stage must be 'vertex', 'fragment', or 'compute'"); + } + + var p_output = Module['_malloc'](4); + var p_output_len = Module['_malloc'](4); + var id = ccall('convert_glsl_to_spirv', + 'number', + ['string', 'number', 'boolean', 'number', 'number'], + [glsl, shader_stage_int, gen_debug, p_output, p_output_len]); + var output = getValue(p_output, 'i32'); + var output_len = getValue(p_output_len, 'i32'); + Module['_free'](p_output); + Module['_free'](p_output_len); + + if (id === 0) { + throw new Error('GLSL compilation failed'); + } + + var ret = {}; + var outputIndexU32 = output / 4; + ret.data = Module['HEAPU32'].subarray(outputIndexU32, outputIndexU32 + output_len); + ret.free = function() { + Module['_destroy_output_buffer'](id); + }; + + return ret; +}; + +Module['compileGLSL'] = function(glsl, shader_stage, gen_debug) { + var compiled = Module['compileGLSLZeroCopy'](glsl, shader_stage, gen_debug); + var ret = compiled.data.slice() + compiled.free(); + return ret; +};