diff --git a/libs/matdbg/CMakeLists.txt b/libs/matdbg/CMakeLists.txt index c8c5976715..ef2e35676b 100644 --- a/libs/matdbg/CMakeLists.txt +++ b/libs/matdbg/CMakeLists.txt @@ -70,18 +70,22 @@ include_directories(${PUBLIC_HDR_DIR} ${RESOURCE_DIR}) add_library(${TARGET} STATIC ${PUBLIC_HDRS} ${SRCS}) target_link_libraries(${TARGET} PUBLIC - utils - filaflat - filabridge backend_headers civetweb - utils - SPIRV - SPIRV-Tools - spirv-cross-glsl + filabridge + filaflat + filamat + glslang matdbg_resources + SPIRV + spirv-cross-glsl + SPIRV-Tools + utils + utils ) +target_include_directories(${TARGET} PRIVATE ${filamat_SOURCE_DIR}/src) + target_include_directories(${TARGET} PUBLIC ${PUBLIC_HDR_DIR}) # ================================================================================================== diff --git a/libs/matdbg/README.md b/libs/matdbg/README.md index 0d91cbf596..cde88e0275 100644 --- a/libs/matdbg/README.md +++ b/libs/matdbg/README.md @@ -1,6 +1,9 @@ # matdbg -1. [User Instructions](#user-instructions) +1. [Capabilities](#capabilities) +1. [Setup for Desktop](#setup-for-desktop) +1. [Setup for Android](#setup-for-android) +1. [Debugger Usage](#debugger-usage) 1. [Architecture Overview](#architecture-overview) 1. [C++ Server](#c-server) 1. [JavaScript Client](#javascript-client) @@ -10,6 +13,20 @@ 1. [Screenshot](#screenshot) 1. [Material Chunks](#material-chunks) +## Capabilities + +matdbg is a library and web application that enables debugging and live-editing of Filament shaders. +At the time of this writing, the following capabilities are supported. + +- OpenGL: Editing GLSL +- Metal: Editing MSL +- Vulkan: Editing transpiled GLSL, displaying disassembled SPIR-V + +Note that a given material can be built with multiple backends, even though only one backend +is active in a particular session. For example, if the current app is using Vulkan, it is still +possible to inspect the Metal shaders, as long as the material has been built with Metal support +included. + ## Setup for Desktop When using the easy build script, include the `-d` argument. For example: @@ -47,6 +64,10 @@ following: This lets you go to http://localhost:8081 in Chrome on your host machine. +Note that we generally use a release build of Filament when running on Android, so the shaders +are optimized and very unreadable. This can be avoided by modifying the build such that `-g` is +passed to matc even in release builds. + ## Debugger Usage After opening the matdbg page in your browser, the usual first step is to select a material in the @@ -219,14 +240,16 @@ not including the terminating null. ## Wish List -- Allow viewing and editing GLSL for Metal and Vulkan (use filamat or spirv-cross for this) -- Stop piggybacking on `type=glsl` for Metal Shading Language. +- Allow editing of the original GLSL, perhaps by enhancing the `-g` option in matc and adding new chunk types. +- Port the web side to TypeScript + - This will clarify the structure of the pseudo-database, which is currently a total hack. + - Allows us to use enums instead of strings in several places (e.g. getShaderAPI) + - Try using https://github.com/basarat/typescript-script because webpack etc is painful. + - If the above idea is too slow then use https://github.com/evanw/esbuild. - Expose the entire `engine.debug` struct in the web UI. - When shader errors occur, send them back over the wire to the web client. - Resizing the Chrome window causes layout issues. - The sidebar in the web app is not resizeable. -- Refactor shader selection stuff to have "index" and "stage" attributes instead of glindex/vkindex/metalindex. - - Alternatively do something similar to makeKey in `MaterialChunk`. - For the material ids, SHA-1 would be better than murmur since the latter can easily have collisions. - It would be easy to add diff decorations to the editor in our `onEdit` function: 1. Examine "changes" (IModelContentChange) to get a set of line numbers. diff --git a/libs/matdbg/include/matdbg/ShaderReplacer.h b/libs/matdbg/include/matdbg/ShaderReplacer.h index e4a0d0fb6b..c547f505b7 100644 --- a/libs/matdbg/include/matdbg/ShaderReplacer.h +++ b/libs/matdbg/include/matdbg/ShaderReplacer.h @@ -35,6 +35,8 @@ public: const uint8_t* getEditedPackage() const; size_t getEditedSize() const; private: + bool replaceSpirv(backend::ShaderModel shaderModel, uint8_t variant, + backend::ShaderType stage, const char* sourceString, size_t stringLength); const backend::Backend mBackend; filaflat::ChunkContainer mOriginalPackage; filaflat::ChunkContainer* mEditedPackage = nullptr; diff --git a/libs/matdbg/src/DebugServer.cpp b/libs/matdbg/src/DebugServer.cpp index 6816d35082..3b9af16a3a 100644 --- a/libs/matdbg/src/DebugServer.cpp +++ b/libs/matdbg/src/DebugServer.cpp @@ -18,16 +18,17 @@ #include +#include #include #include #include #include -#include +#include #include #include -#include +#include #include @@ -38,6 +39,8 @@ #include #include +using utils::FixedCapacityVector; + // If set to 0, this serves HTML from a resgen resource. Use 1 only during local development, which // serves files directly from the source code tree. #define SERVE_FROM_SOURCE_TREE 0 @@ -59,6 +62,29 @@ static const StaticString kSuccessHeader = "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n" "Connection: close\r\n\r\n"; +static const StaticString kErrorHeader = + "HTTP/1.1 404 Not Found\r\nContent-Type: %s\r\n" + "Connection: close\r\n\r\n"; + +static void spirvToAsm(struct mg_connection *conn, const uint32_t* spirv, size_t size) { + auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); + spv_text text = nullptr; + const uint32_t options = SPV_BINARY_TO_TEXT_OPTION_INDENT | + SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES; + spvBinaryToText(context, spirv, size / 4, options, &text, nullptr); + + mg_printf(conn, kSuccessHeader.c_str(), "application/txt"); + mg_write(conn, text->str, text->length); + spvTextDestroy(text); + spvContextDestroy(context); +} + +static void spirvToGlsl(struct mg_connection *conn, const uint32_t* spirv, size_t size) { + auto glsl = ShaderExtractor::spirvToGLSL(spirv, size / 4); + mg_printf(conn, kSuccessHeader.c_str(), "application/txt"); + mg_printf(conn, glsl.c_str(), glsl.size()); +} + class FileRequestHandler : public CivetHandler { public: FileRequestHandler(DebugServer* server) : mServer(server) {} @@ -116,10 +142,18 @@ public: std::string uri(request->local_uri); const auto error = [request](int line) { - slog.e << "DebugServer: 404 at " << line << ": " << request->query_string << io::endl; + slog.e << "DebugServer: 404 at line " << line << ": " << request->query_string + << io::endl; return false; }; + const auto softError = [request, conn](const char* msg) { + slog.e << "DebugServer: " << msg << ": " << request->query_string << io::endl; + mg_printf(conn, kErrorHeader.c_str(), "application/txt"); + mg_write(conn, msg, strlen(msg)); + return true; + }; + if (uri == "/api/active") { mServer->updateActiveVariants(); mg_printf(conn, kSuccessHeader.c_str(), "application/json"); @@ -209,11 +243,17 @@ public: return true; } + const StaticString glsl("glsl"); + const StaticString msl("msl"); + const StaticString spirv("spirv"); + char type[6] = {}; if (mg_get_var(request->query_string, qlength, "type", type, sizeof(type)) < 0) { return error(__LINE__); } + CString language(type, strlen(type)); + char glindex[4] = {}; char vkindex[4] = {}; char metalindex[4] = {}; @@ -230,7 +270,11 @@ public: } if (glindex[0]) { - std::vector info(getShaderCount(package, ChunkType::MaterialGlsl)); + if (language != glsl) { + return softError("Only GLSL is supported."); + } + + FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialGlsl)); if (!getGlShaderInfo(package, info.data())) { return error(__LINE__); } @@ -261,7 +305,7 @@ public: } filaflat::ShaderBuilder builder; - std::vector info(getShaderCount(package, ChunkType::MaterialSpirv)); + FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialSpirv)); if (!getVkShaderInfo(package, info.data())) { return error(__LINE__); } @@ -274,25 +318,17 @@ public: const auto& item = info[shaderIndex]; extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, builder); - // TODO: Add a transpiler that depends on "type" and add an MSL type instead of - // piggybacking on type=GLSL. - - mg_printf(conn, kSuccessHeader.c_str(), "application/text"); - - if (true) { - auto context = spvContextCreate(SPV_ENV_UNIVERSAL_1_0); - spv_text text = nullptr; - const uint32_t options = SPV_BINARY_TO_TEXT_OPTION_INDENT | - SPV_BINARY_TO_TEXT_OPTION_FRIENDLY_NAMES; - const uint32_t* data = (const uint32_t*) builder.data(); - spvBinaryToText(context, data, builder.size() / 4, options, &text, nullptr); - - mg_write(conn, text->str, text->length); - spvTextDestroy(text); - spvContextDestroy(context); + if (language == spirv) { + spirvToAsm(conn, (const uint32_t*) builder.data(), builder.size()); + return true; } - return true; + if (language == glsl) { + spirvToGlsl(conn, (const uint32_t*) builder.data(), builder.size()); + return true; + } + + return softError("Only SPIRV is supported."); } if (metalindex[0]) { @@ -302,7 +338,7 @@ public: } filaflat::ShaderBuilder builder; - std::vector info(getShaderCount(package, ChunkType::MaterialMetal)); + FixedCapacityVector info(getShaderCount(package, ChunkType::MaterialMetal)); if (!getMetalShaderInfo(package, info.data())) { return error(__LINE__); } @@ -315,9 +351,13 @@ public: const auto& item = info[shaderIndex]; extractor.getShader(item.shaderModel, item.variant, item.pipelineStage, builder); - mg_printf(conn, kSuccessHeader.c_str(), "application/txt"); - mg_write(conn, builder.data(), builder.size() - 1); - return true; + if (language == msl) { + mg_printf(conn, kSuccessHeader.c_str(), "application/txt"); + mg_write(conn, builder.data(), builder.size() - 1); + return true; + } + + return softError("Only MSL is supported."); } return error(__LINE__); @@ -542,11 +582,12 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api return error(__LINE__); } - std::vector infos; + FixedCapacityVector infos; size_t shaderCount; switch (api) { case backend::Backend::OPENGL: { shaderCount = getShaderCount(package, ChunkType::MaterialGlsl); + infos.reserve(shaderCount); infos.resize(shaderCount); if (!getGlShaderInfo(package, infos.data())) { return error(__LINE__); @@ -555,6 +596,7 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api } case backend::Backend::VULKAN: { shaderCount = getShaderCount(package, ChunkType::MaterialSpirv); + infos.reserve(shaderCount); infos.resize(shaderCount); if (!getVkShaderInfo(package, infos.data())) { return error(__LINE__); @@ -563,6 +605,7 @@ bool DebugServer::handleEditCommand(const MaterialKey& key, backend::Backend api } case backend::Backend::METAL: { shaderCount = getShaderCount(package, ChunkType::MaterialMetal); + infos.reserve(shaderCount); infos.resize(shaderCount); if (!getMetalShaderInfo(package, infos.data())) { return error(__LINE__); diff --git a/libs/matdbg/src/ShaderExtractor.cpp b/libs/matdbg/src/ShaderExtractor.cpp index 6c2fc82be3..a8a7e86b70 100644 --- a/libs/matdbg/src/ShaderExtractor.cpp +++ b/libs/matdbg/src/ShaderExtractor.cpp @@ -92,7 +92,8 @@ CString ShaderExtractor::spirvToGLSL(const uint32_t* data, size_t wordCount) { using namespace spirv_cross; CompilerGLSL::Options emitOptions; - emitOptions.es = true; + emitOptions.es = false; + emitOptions.version = 410; emitOptions.vulkan_semantics = true; vector spirv(data, data + wordCount); diff --git a/libs/matdbg/src/ShaderReplacer.cpp b/libs/matdbg/src/ShaderReplacer.cpp index 827f415299..62e5e68df7 100644 --- a/libs/matdbg/src/ShaderReplacer.cpp +++ b/libs/matdbg/src/ShaderReplacer.cpp @@ -18,27 +18,33 @@ #include +#include + #include #include #include -#include +#include + +#include + +#include "sca/builtinResource.h" +#include "sca/GLSLTools.h" namespace filament { namespace matdbg { using namespace backend; using namespace filaflat; +using namespace filamat; +using namespace glslang; using namespace std; using namespace tsl; using namespace utils; -using filamat::ChunkType; - -// Utility class for managing a pair of lists: a list of shader records, and a list of string lines -// that can be encoded into an index list. Also knows how to read & write the relevant IFF chunks. +// Tiny database of shader text that can import / export MaterialTextChunk and DictionaryTextChunk. class ShaderIndex { public: // Consumes a chunk and builds the string list. @@ -55,7 +61,7 @@ public: // Replaces the specified shader text with new content. void replaceShader(backend::ShaderModel shaderModel, uint8_t variant, - backend::ShaderType stage, const char* source, size_t sourceLength); + ShaderType stage, const char* source, size_t sourceLength); bool isEmpty() const { return mStringLines.size() == 0 && mShaderRecords.size() == 0; } @@ -77,6 +83,42 @@ private: vector mStringLines; }; +// Tiny database of data blobs that can import / export MaterialSpirvChunk and DictionarySpirvChunk. +// The blobs are stored *after* they have been compressed by SMOL-V. +class BlobIndex { +public: + // Consumes a chunk and builds the blob list. + void addDataBlobs(const uint8_t* chunkContent, size_t size); + + // Consumes a chunk and builds the shader records. + void addShaderRecords(const uint8_t* chunkContent, size_t size); + + // Produces a chunk holding the blob list. + void writeBlobsChunk(ChunkType tag, ostream& stream) const; + + // Produces a chunk holding the shader records. + void writeShadersChunk(ChunkType tag, ostream& stream) const; + + // Replaces the specified shader with new content. + void replaceShader(backend::ShaderModel shaderModel, uint8_t variant, + ShaderType stage, const char* source, size_t sourceLength); + + bool isEmpty() const { return mDataBlobs.size() == 0 && mShaderRecords.size() == 0; } + +private: + struct ShaderRecord { + uint8_t model; + uint8_t variant; + uint8_t stage; + uint32_t blobIndex; + }; + + using SpirvBlob = vector; + + vector mShaderRecords; + vector mDataBlobs; +}; + ShaderReplacer::ShaderReplacer(Backend backend, const void* data, size_t size) : mBackend(backend), mOriginalPackage(data, size) { switch (backend) { @@ -101,20 +143,19 @@ ShaderReplacer::~ShaderReplacer() { delete mEditedPackage; } -bool ShaderReplacer::replaceShaderSource(backend::ShaderModel shaderModel, uint8_t variant, - backend::ShaderType stage, const char* source, size_t sourceLength) { +bool ShaderReplacer::replaceShaderSource(ShaderModel shaderModel, uint8_t variant, + ShaderType stage, const char* source, size_t sourceLength) { if (!mOriginalPackage.parse()) { return false; } - ChunkContainer const& cc = mOriginalPackage; + filaflat::ChunkContainer const& cc = mOriginalPackage; if (!cc.hasChunk(mMaterialTag) || !cc.hasChunk(mDictionaryTag)) { return false; } if (mDictionaryTag == ChunkType::DictionarySpirv) { - slog.e << "SPIR-V editing is not yet supported." << io::endl; - return false; + return replaceSpirv(shaderModel, variant, stage, source, sourceLength); } // Clone all chunks except Dictionary* and Material*. @@ -159,6 +200,93 @@ bool ShaderReplacer::replaceShaderSource(backend::ShaderModel shaderModel, uint8 return true; } +bool ShaderReplacer::replaceSpirv(ShaderModel shaderModel, uint8_t variant, + ShaderType stage, const char* source, size_t sourceLength) { + filaflat::ChunkContainer const& cc = mOriginalPackage; + + assert_invariant(mMaterialTag == ChunkType::MaterialSpirv); + + const EShLanguage shLang = stage == VERTEX ? EShLangVertex : EShLangFragment; + + std::string nullTerminated(source, sourceLength); + source = nullTerminated.c_str(); + + TShader tShader(shLang); + tShader.setStrings(&source, 1); + + MaterialBuilder::TargetApi targetApi = targetApiFromBackend(mBackend); + assert_invariant(targetApi == MaterialBuilder::TargetApi::VULKAN); + + const int langVersion = GLSLTools::glslangVersionFromShaderModel(shaderModel); + const EShMessages msg = GLSLTools::glslangFlagsFromTargetApi(targetApi); + const bool ok = tShader.parse(&DefaultTBuiltInResource, langVersion, false, msg); + if (!ok) { + slog.e << "ShaderReplacer parse:\n" << tShader.getInfoLog() << io::endl; + return false; + } + + TProgram program; + program.addShader(&tShader); + const bool linkOk = program.link(msg); + if (!linkOk) { + slog.e << "ShaderReplacer link:\n" << program.getInfoLog() << io::endl; + return false; + } + + // Unfortunately we need to use std::vector to interface with glslang. + vector spirv; + + SpvOptions options; + options.generateDebugInfo = true; + GlslangToSpv(*tShader.getIntermediate(), spirv, &options); + + source = (const char*) spirv.data(); + sourceLength = spirv.size() * 4; + + slog.i << "Success re-generating SPIR-V. (" << sourceLength << " bytes)" << io::endl; + + // Clone all chunks except Dictionary* and Material*. + stringstream sstream(string((const char*) cc.getData(), cc.getSize())); + stringstream tstream; + BlobIndex shaderIndex; + { + uint64_t type; + uint32_t size; + vector content; + while (sstream) { + sstream.read((char*) &type, sizeof(type)); + sstream.read((char*) &size, sizeof(size)); + content.resize(size); + sstream.read((char*) content.data(), size); + if (ChunkType(type) == mDictionaryTag) { + shaderIndex.addDataBlobs(content.data(), size); + continue; + } + if (ChunkType(type) == mMaterialTag) { + shaderIndex.addShaderRecords(content.data(), size); + continue; + } + tstream.write((char*) &type, sizeof(type)); + tstream.write((char*) &size, sizeof(size)); + tstream.write((char*) content.data(), size); + } + } + + // Append the new chunks for Dictionary* and Material*. + if (!shaderIndex.isEmpty()) { + shaderIndex.replaceShader(shaderModel, variant, stage, source, sourceLength); + shaderIndex.writeBlobsChunk(mDictionaryTag, tstream); + shaderIndex.writeShadersChunk(mMaterialTag, tstream); + } + + // Copy the new package from the stringstream into a ChunkContainer. + const size_t size = tstream.str().size(); + uint8_t* data = new uint8_t[size]; + memcpy(data, tstream.str().data(), size); + mEditedPackage = new filaflat::ChunkContainer(data, size); + return true; +} + const uint8_t* ShaderReplacer::getEditedPackage() const { return (const uint8_t*) mEditedPackage->getData(); } @@ -333,6 +461,99 @@ void ShaderIndex::encodeShadersToIndices() { } } +void BlobIndex::addDataBlobs(const uint8_t* chunkContent, size_t size) { + const uint32_t compression = *((const uint32_t*) chunkContent); + chunkContent += 4; + const uint32_t blobCount = *((const uint32_t*) chunkContent); + chunkContent += 4; + mDataBlobs.resize(blobCount); + const uint8_t* ptr = chunkContent; + for (uint32_t i = 0; i < blobCount; i++) { + const uint64_t byteCount = *((const uint64_t*) ptr); + ptr += sizeof(uint64_t); + mDataBlobs[i].resize(byteCount); + memcpy(mDataBlobs[i].data(), ptr, byteCount); + ptr += byteCount; + } +} + +void BlobIndex::addShaderRecords(const uint8_t* chunkContent, size_t size) { + stringstream stream(string((const char*) chunkContent, size)); + uint64_t recordCount; + stream.read((char*) &recordCount, sizeof(recordCount)); + mShaderRecords.resize(recordCount); + for (auto& record : mShaderRecords) { + stream.read((char*) &record.model, sizeof(ShaderRecord::model)); + stream.read((char*) &record.variant, sizeof(ShaderRecord::variant)); + stream.read((char*) &record.stage, sizeof(ShaderRecord::stage)); + stream.read((char*) &record.blobIndex, sizeof(ShaderRecord::blobIndex)); + } +} + +void BlobIndex::writeBlobsChunk(ChunkType tag, ostream& stream) const { + // First perform a prepass to compute chunk size. + uint32_t size = sizeof(uint32_t) + sizeof(uint32_t); + for (const auto& blob : mDataBlobs) { + size += sizeof(uint64_t); + size += blob.size(); + } + + // Serialize the chunk. + const uint64_t type = tag; + stream.write((char*) &type, sizeof(type)); + stream.write((char*) &size, sizeof(size)); + const uint32_t compression = 1; + stream.write((char*) &compression, sizeof(compression)); + const uint32_t count = mDataBlobs.size(); + stream.write((char*) &count, sizeof(count)); + for (const auto& blob : mDataBlobs) { + const uint64_t byteCount = blob.size(); + stream.write((char*) &byteCount, sizeof(byteCount)); + stream.write((char*) blob.data(), blob.size()); + } +} + +void BlobIndex::writeShadersChunk(ChunkType tag, ostream& stream) const { + // First perform a prepass to compute chunk size. + uint32_t size = sizeof(uint64_t); + for (const auto& record : mShaderRecords) { + size += sizeof(ShaderRecord::model); + size += sizeof(ShaderRecord::variant); + size += sizeof(ShaderRecord::stage); + size += sizeof(ShaderRecord::blobIndex); + } + + // Serialize the chunk. + uint64_t type = tag; + stream.write((char*) &type, sizeof(type)); + stream.write((char*) &size, sizeof(size)); + const uint64_t recordCount = mShaderRecords.size(); + stream.write((char*) &recordCount, sizeof(recordCount)); + for (const auto& record : mShaderRecords) { + stream.write((char*) &record.model, sizeof(ShaderRecord::model)); + stream.write((char*) &record.variant, sizeof(ShaderRecord::variant)); + stream.write((char*) &record.stage, sizeof(ShaderRecord::stage)); + stream.write((char*) &record.blobIndex, sizeof(ShaderRecord::blobIndex)); + } +} + +void BlobIndex::replaceShader(ShaderModel shaderModel, uint8_t variant, + ShaderType stage, const char* source, size_t sourceLength) { + smolv::ByteArray compressed; + if (!smolv::Encode(source, sourceLength, compressed, 0)) { + utils::slog.e << "Error with SPIRV compression" << utils::io::endl; + return; + } + const uint8_t model = (uint8_t) shaderModel; + for (auto& record : mShaderRecords) { + if (record.model == model && record.variant == variant && record.stage == stage) { + auto& blob = mDataBlobs[record.blobIndex]; + blob.resize(compressed.size()); + memcpy(blob.data(), compressed.data(), compressed.size()); + break; + } + } +} } // namespace matdbg } // namespace filament diff --git a/libs/matdbg/web/script.js b/libs/matdbg/web/script.js index a53c8daaba..79d997e6c3 100644 --- a/libs/matdbg/web/script.js +++ b/libs/matdbg/web/script.js @@ -48,13 +48,28 @@ window.MonacoEnvironment = { } }; -// TODO: this function could be simplified by changing the format of the shader selector. +function getShaderAPI(selection) { + if (!selection) { + selection = gCurrentShader; + } + if ("glindex" in selection) return "opengl"; + if ("vkindex" in selection) return "vulkan"; + if ("metalindex" in selection) return "metal"; + return "error"; +} + function rebuildMaterial() { let api = 0, index = -1; - if ("glindex" in gCurrentShader) { api = 1; index = gCurrentShader.glindex; } - if ("vkindex" in gCurrentShader) { api = 2; index = gCurrentShader.vkindex; } - if ("metalindex" in gCurrentShader) { api = 3; index = gCurrentShader.metalindex; } - const editedText = getShaderRecord(gCurrentShader).text; + if (gCurrentLanguage === "spirv") { + console.error("SPIR-V editing is not supported."); + return; + } + switch (getShaderAPI()) { + case "opengl": api = 1; index = gCurrentShader.glindex; break; + case "vulkan": api = 2; index = gCurrentShader.vkindex; break; + case "metal": api = 3; index = gCurrentShader.metalindex; break; + } + const editedText = getShaderRecord(gCurrentShader)[gCurrentLanguage]; const byteCount = new Blob([editedText]).size; gSocket.send(`EDIT ${gCurrentShader.matid} ${api} ${index} ${byteCount} ${editedText}`); } @@ -87,7 +102,7 @@ document.querySelector("body").addEventListener("click", (evt) => { for (const lang of "glsl spirv msl".split(" ")) { if (anchor.classList.contains(lang)) { gCurrentLanguage = lang; - renderShaderStatus(); + selectShader(gCurrentShader); return; } } @@ -227,23 +242,28 @@ function fetchMaterials() { } function fetchShader(selection, matinfo, onDone) { - let query, target; - if (selection.glindex >= 0) { - query = `type=glsl&glindex=${selection.glindex}`; - target = matinfo.opengl[parseInt(selection.glindex)]; - } - if (selection.vkindex >= 0) { - query = `type=spirv&vkindex=${selection.vkindex}`; - target = matinfo.vulkan[parseInt(selection.vkindex)]; - } - if (selection.metalindex >= 0) { - query = `type=glsl&metalindex=${selection.metalindex}`; - target = matinfo.metal[parseInt(selection.metalindex)]; + let query, target, index; + switch (getShaderAPI(selection)) { + case "opengl": + index = parseInt(selection.glindex); + query = `type=${gCurrentLanguage}&glindex=${index}`; + target = matinfo.opengl[index]; + break; + case "vulkan": + index = parseInt(selection.vkindex); + query = `type=${gCurrentLanguage}&vkindex=${index}`; + target = matinfo.vulkan[index]; + break; + case "metal": + index = parseInt(selection.metalindex); + query = `type=${gCurrentLanguage}&metalindex=${index}`; + target = matinfo.metal[index]; + break; } fetch(`api/shader?matid=${matinfo.matid}&${query}`).then(function(response) { return response.text(); }).then(function(shaderText) { - target.text = shaderText; + target[gCurrentLanguage] = shaderText; onDone(); }); } @@ -347,15 +367,19 @@ function renderShaderStatus() { const glsl = "glsl " + (gCurrentLanguage === "glsl" ? "active" : ""); const msl = "msl " + (gCurrentLanguage === "msl" ? "active" : ""); const spirv = "spirv " + (gCurrentLanguage === "spirv" ? "active" : ""); - if (gCurrentShader.metalindex >= 0) { - statusString += `   [GLSL]`; - statusString += `   [MSL]`; + switch (getShaderAPI()) { + case "opengl": + statusString += `   [GLSL]`; + break; + case "metal": + statusString += `   [MSL]`; + break; + case "vulkan": + statusString += `   [GLSL]`; + statusString += `   [SPIRV]`; + break; } - if (gCurrentShader.vkindex >= 0) { - statusString += `   [GLSL]`; - statusString += `   [SPIRV]`; - } - if (shader.modified) { + if (shader.modified && gCurrentLanguage !== "spirv") { statusString += "   [rebuild]"; } if (!shader.active) { @@ -371,17 +395,37 @@ function selectShader(selection) { console.error("Shader not yet available.") return; } + + // Change the current language selection if necessary. + switch (getShaderAPI(selection)) { + case "opengl": + if (gCurrentLanguage != "glsl") { + gCurrentLanguage = "glsl"; + } + break; + case "vulkan": + if (gCurrentLanguage != "spirv" && gCurrentLanguage != "glsl") { + gCurrentLanguage = "spirv"; + } + break; + case "metal": + if (gCurrentLanguage != "msl") { + gCurrentLanguage = "msl"; + } + break; + } + const showShaderSource = () => { gCurrentShader = selection; gCurrentShader.matid = gCurrentMaterial; renderMaterialDetail(); gEditorIsLoading = true; - gEditor.setValue(shader.text); + gEditor.setValue(shader[gCurrentLanguage]); gEditorIsLoading = false; shaderSource.style.visibility = "visible"; renderShaderStatus(); }; - if (!shader.text) { + if (!shader[gCurrentLanguage]) { const matInfo = gMaterialDatabase[gCurrentMaterial]; fetchShader(selection, matInfo, showShaderSource); } else { @@ -401,7 +445,7 @@ function onEdit(changes) { shader.modified = true; renderShaderStatus(); } - shader.text = gEditor.getValue(); + shader[gCurrentLanguage] = gEditor.getValue(); } function selectMaterial(matid, selectFirstShader) { diff --git a/tools/matc/CMakeLists.txt b/tools/matc/CMakeLists.txt index cb8c93491f..1b54498adc 100644 --- a/tools/matc/CMakeLists.txt +++ b/tools/matc/CMakeLists.txt @@ -9,7 +9,7 @@ set(TARGET matlang) # ================================================================================================== # Sources and headers # ================================================================================================== -file(GLOB_RECURSE HDRS +set(HDRS src/matc/CommandlineConfig.h src/matc/Compiler.h src/matc/Config.h