/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "StdAfx.h" #include "RemoteCompiler.h" #include "../RenderCapabilities.h" #include #include #include #include #include #if defined(AZ_RESTRICTED_PLATFORM) #undef AZ_RESTRICTED_SECTION #define REMOTECOMPILER_CPP_SECTION_1 1 #define REMOTECOMPILER_CPP_SECTION_2 2 #define REMOTECOMPILER_CPP_SECTION_3 3 #define REMOTECOMPILER_CPP_SECTION_4 4 #endif #if defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) #undef AZ_RESTRICTED_SECTION #define REMOTECOMPILER_CPP_SECTION_2 2 #endif #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_3 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/RemoteCompiler_cpp_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/RemoteCompiler_cpp_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/RemoteCompiler_cpp_salem.inl" #endif #endif namespace NRemoteCompiler { //Debugging the network connection problems can be tricky without verbose logging, more verbose //than anyone would like on by default. This is a special situation in which if logging is off by default //we would normally use a define or cvar to turn verbose logging on. However you really can't do //that here because of networking, you can't easily break or open the console and enter the //cvar command to turn verbose logging on fast enough to catch logs around the error condition without //causing different code paths due to breaking. So this is an automatic verbose logging var that is off //by default and turns on for a limited number of log lines after the error is logged then automaticly //turns itself off so we are not spammed for to long so as to not adversely affect the rest of the systems. int s_verboselogging = 0; bool VerboseLogging(bool start = false) { if(start) { s_verboselogging = 100; return true; } if(s_verboselogging > 0) { s_verboselogging--; return true; } return false; } // Note: Cry's original source uses little endian as their internal communication endianness // so this new code will do the same. // RemoteProxyState: store the current state of things required to communicate to the remote server // via the Engine Connection, so that its outside this interface and protected from the details of it. class RemoteProxyState { public: unsigned int m_remoteRequestCRC; unsigned int m_remoteResponseCRC; unsigned int m_nextAssignedToken; bool m_unitTestMode; bool m_engineConnectionCallbackInstalled; // lazy-install it. typedef AZStd::function TResponseCallback; RemoteProxyState() { m_engineConnectionCallbackInstalled = false; m_unitTestMode = false; m_remoteRequestCRC = AZ_CRC("ShaderCompilerProxyRequest"); m_remoteResponseCRC = AZ_CRC("ShaderCompilerProxyResponse"); m_nextAssignedToken = 0; } #if defined(AZ_TESTS_ENABLED) void SetUnitTestMode(bool newMode) { m_unitTestMode = newMode; } #endif // AZ_TESTS_ENABLED bool SubmitRequestAndBlockForResponse(std::vector& inout) { unsigned int chosenToken = m_nextAssignedToken++; AzFramework::SocketConnection* engineConnection = AzFramework::SocketConnection::GetInstance(); if (!m_unitTestMode) { // if we're not in unit test mode, we NEED an engine connection if (!engineConnection) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Compile: no engine connection present, but r_AssetProcessorShaderCompiler is set in config!\n"); VerboseLogging(true); return false; } // install the callback the first time its needed: if (!m_engineConnectionCallbackInstalled) { // (AddTypeCallback is assumed to be thread safe.) m_engineConnectionCallbackInstalled = true; engineConnection->AddMessageHandler(m_remoteResponseCRC, std::bind(&RemoteProxyState::OnReceiveRemoteResponse, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)); } } // the plan: Create a wait event // then raise the event when we get a response from the server // then wait for the event to be raised. CryEvent waitEvent; { CryAutoLock protector(m_mapProtector); // scoped lock on the map // now create an anonymous function that will copy the data and set the waitevent when the callback is triggered: m_responsesAwaitingCallback[chosenToken] = [&inout, &waitEvent](const void* payload, unsigned int payloadSize) { inout.resize(payloadSize); memcpy(inout.data(), payload, payloadSize); waitEvent.Set(); }; } #if defined(AZ_TESTS_ENABLED) if (m_unitTestMode) { // if we're unit testing, there WILL BE NO ENGINE CONNECTION // we must respond as if we're the engine connection // you can assume the payload has already been unit tested to conform. // instead, just write the data to the buffer as expected: std::vector newData; if (memcmp(inout.data(), "empty", 5) == 0) { // unit test to send empty } else if (memcmp(inout.data(), "incomplete", 10) == 0) { // unit test to send incomplete data. newData.push_back('x'); } else if (memcmp(inout.data(), "corrupt", 7) == 0) { // unit test to send corrupt data std::string testString("CDCDCDCDCDCDCDCD"); newData.assign(testString.begin(), testString.end()); } else if ((memcmp(inout.data(), "compile_failure", 15) == 0) || (memcmp(inout.data(), "success", 7) == 0)) { // simulate compile failure response // [payload length 4 bytes (LITTLE ENDIAN) ] [status 1 byte] [payload] // and payload consists of // [ uncompressed size (NETWORK BYTE ORDER) ] [payload (compressed)] bool isFail = (memcmp(inout.data(), "compile_failure", 15) == 0); std::string failreason("decompressed_plaintext"); size_t uncompressed_size = failreason.size(); size_t compressed_size = uncompressed_size; std::vector compressedData; compressed_size = uncompressed_size * 2; compressedData.resize(compressed_size); gEnv->pSystem->CompressDataBlock(failreason.data(), failreason.size(), compressedData.data(), compressed_size); compressedData.resize(compressed_size); // first four bytes are payload size. // firth byte is status unsigned int payloadSize = 4 + compressed_size; newData.resize(4 + 1 + payloadSize); uint8 status_code = isFail ? 0x05 : 0x01; // 5 is fail, 1 is ok unsigned int uncompressed_size_le = uncompressed_size; SwapEndian(uncompressed_size_le); memcpy(newData.data(), &payloadSize, 4); memcpy(newData.data() + 4, &status_code, 0x01); // 0x05 = error compiling memcpy(newData.data() + 4 + 1, &uncompressed_size_le, 4); memcpy(newData.data() + 4 + 1 + 4, compressedData.data(), compressedData.size()); } else { newData.clear(); } // place the messageID at the end: newData.resize(newData.size() + 4); // for the messageID unsigned int swappedToken = chosenToken; SwapEndian(swappedToken); memcpy(newData.data() + newData.size() - 4, &swappedToken, 4); OnReceiveRemoteResponse(m_remoteResponseCRC, AzFramework::AssetSystem::DEFAULT_SERIAL, newData.data(), newData.size()); } else // note: This else is inside the endif so that it only takes the second branch if UNIT TEST MODE is present. #endif // AZ_TESTS_ENABLED { // append the messageID: inout.resize(inout.size() + 4); // for the messageID unsigned int swappedToken = chosenToken; SwapEndian(swappedToken); memcpy(inout.data() + inout.size() - 4, &swappedToken, 4); if (!engineConnection->SendMsg(m_remoteRequestCRC, inout.data(), inout.size())) { AZ_Error("RemoteCompiler", false, "CShaderSrv::SubmitRequestAndBlockForResponse() : unable to send via engine connection, but r_AssetProcessorShaderCompiler is set in config!\n"); VerboseLogging(true); return false; } } if (!waitEvent.Wait(10000)) { // failure to get response! AZ_Error("RemoteCompiler", false, "CShaderSrv::SubmitRequestAndBlockForResponse() : no response received!\n"); VerboseLogging(true); CryAutoLock protector(m_mapProtector); m_responsesAwaitingCallback.erase(chosenToken); if(!gEnv->IsInToolMode()) { EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, "Remote Shader Compiler", "Unable to connect to Remote Shader Compiler", false); } return false; } // wait succeeded! We got a response! Unblock and return! return true; } private: CryMutex m_mapProtector; std::unordered_map m_responsesAwaitingCallback; void OnReceiveRemoteResponse(unsigned int messageID, unsigned int /*serial*/, const void* payload, unsigned int payloadSize) { // peel back to inner payload: if (payloadSize < 4) { // indicate error! AZ_Error("RemoteCompiler", false, " CShaderSrv::OnReceiveRemoteREsponse() : truncated message from shader compiler proxy"); VerboseLogging(true); return; } // last four bytes are expected to be the response ID const uint8* payload_start = reinterpret_cast(payload); const uint8* end_payload = payload_start + payloadSize; const uint8* messageIDPtr = end_payload - 4; unsigned int responseId = *reinterpret_cast(messageIDPtr); SwapEndian(responseId); CryAutoLock protector(m_mapProtector); auto callbackToCall = m_responsesAwaitingCallback.find(responseId); if (callbackToCall == m_responsesAwaitingCallback.end()) { AZ_Error("RemoteCompiler", false, "CShaderSrv::OnReceiveRemoteResponse() : Unexpected response from shader compiler proxy."); VerboseLogging(true); return; } // give only the inner payload back to the callee! callbackToCall->second(payload_start, payloadSize - sizeof(unsigned int)); m_responsesAwaitingCallback.erase(callbackToCall); } }; CShaderSrv::CShaderSrv() { #if defined(AZ_TESTS_ENABLED) m_unitTestMode = false; #endif // AZ_TESTS_ENABLED Init(); } void CShaderSrv::Init() { static RemoteProxyState proxyState; m_remoteState = &proxyState; int result = AZ::AzSock::Startup(); if (AZ::AzSock::SocketErrorOccured(result)) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Init() : Could not init root socket\n"); VerboseLogging(true); return; } m_RequestLineRootFolder = ""; ICVar* pGameFolder = gEnv->pConsole->GetCVar("sys_game_folder"); ICVar* pCompilerFolderSuffix = CRenderer::CV_r_ShaderCompilerFolderSuffix; if (pGameFolder) { string folder = pGameFolder->GetString(); folder.Trim(); if (!folder.empty()) { if (pCompilerFolderSuffix) { string suffix = pCompilerFolderSuffix->GetString(); suffix.Trim(); folder.append(suffix); } m_RequestLineRootFolder = folder + string("/"); } } if (m_RequestLineRootFolder.empty()) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Init() : Game folder has not been specified\n"); VerboseLogging(true); } } CShaderSrv& CShaderSrv::Instance() { static CShaderSrv g_ShaderSrv; return g_ShaderSrv; } EShaderCompiler CShaderSrv::GetShaderCompiler() const { EShaderCompiler shaderCompiler = eSC_Unknown; EShaderLanguage shaderLanguage = GetShaderLanguage(); switch (shaderLanguage) { case eSL_Orbis: shaderCompiler = eSC_Orbis_DXC; break; case eSL_Durango: shaderCompiler = eSC_Durango_FXC; break; case eSL_D3D11: shaderCompiler = eSC_D3D11_FXC; break; case eSL_GL4_1: case eSL_GL4_4: case eSL_GLES3_0: case eSL_GLES3_1: shaderCompiler = gRenDev->m_cEF.HasStaticFlag(HWSST_LLVM_DIRECTX_SHADER_COMPILER) ? eSC_GLSL_LLVM_DXC : eSC_GLSL_HLSLcc; break; case eSL_METAL: shaderCompiler = gRenDev->m_cEF.HasStaticFlag(HWSST_LLVM_DIRECTX_SHADER_COMPILER) ? eSC_METAL_LLVM_DXC : eSC_METAL_HLSLcc; break; } return shaderCompiler; } const char *CShaderSrv::GetShaderCompilerName() const { // NOTE: These strings are used in the CrySCompilerServer tool as IDs. static const char *shaderCompilerNames[eSC_MAX] = { "Unknown", "Orbis_DXC", "Durango_FXC", "D3D11_FXC", "GLSL_HLSLcc", "METAL_HLSLcc", "GLSL_LLVM_DXC", "METAL_LLVM_DXC" }; EShaderCompiler shaderCompiler = GetShaderCompiler(); return shaderCompilerNames[shaderCompiler]; } const char* CShaderSrv::GetPlatformName() const { const char *platformName = "Unknown"; switch (CParserBin::m_targetPlatform) { case AZ::PLATFORM_WINDOWS_32: case AZ::PLATFORM_WINDOWS_64: platformName = "PC"; break; #if defined(AZ_EXPAND_FOR_RESTRICTED_PLATFORM) || defined(AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS) #define AZ_RESTRICTED_PLATFORM_EXPANSION(CodeName, CODENAME, codename, PrivateName, PRIVATENAME, privatename, PublicName, PUBLICNAME, publicname, PublicAuxName1, PublicAuxName2, PublicAuxName3)\ case AZ::PLATFORM_##PUBLICNAME: \ platformName = #PrivateName;\ break; #if defined(AZ_EXPAND_FOR_RESTRICTED_PLATFORM) AZ_EXPAND_FOR_RESTRICTED_PLATFORM #else AZ_TOOLS_EXPAND_FOR_RESTRICTED_PLATFORMS #endif #undef AZ_RESTRICTED_PLATFORM_EXPANSION #endif case AZ::PLATFORM_ANDROID: case AZ::PLATFORM_ANDROID_64: platformName = "Android"; break; case AZ::PLATFORM_APPLE_OSX: platformName = "Mac"; break; case AZ::PLATFORM_APPLE_IOS: case AZ::PLATFORM_APPLE_TV: platformName = "iOS"; break; case AZ::PLATFORM_LINUX_64: platformName = "Linux"; break; default: AZ_Assert(false, "Unknown shader platform"); break; } return platformName; } AZStd::string CShaderSrv::GetShaderCompilerFlags(EHWShaderClass eClass, UPipelineState pipelineState, uint32 MDVMask) const { AZStd::string flags = ""; EShaderCompiler shaderCompiler = GetShaderCompiler(); switch (shaderCompiler) { // ---------------------------------------- case eSC_Orbis_DXC: { flags = "%s %s \"%s\" \"%s\""; #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_1 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/RemoteCompiler_cpp_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/RemoteCompiler_cpp_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/RemoteCompiler_cpp_salem.inl" #endif #endif #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/RemoteCompiler_cpp_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/RemoteCompiler_cpp_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/RemoteCompiler_cpp_salem.inl" #endif #endif #if defined(TOOLS_SUPPORT_PROVO) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2 #include "Provo/RemoteCompiler_cpp_provo.inl" #endif #if defined(TOOLS_SUPPORT_XENIA) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2 #include "Xenia/RemoteCompiler_cpp_xenia.inl" #endif #if defined(TOOLS_SUPPORT_SALEM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2 #include "Salem/RemoteCompiler_cpp_salem.inl" #endif } break; // ---------------------------------------- case eSC_Durango_FXC: case eSC_D3D11_FXC: { const char* extraFlags = (shaderCompiler==eSC_Durango_FXC) ? "/Gis" : ""; const char* debugFlags = ""; if (CRenderer::CV_r_shadersdebug == 3) { debugFlags = " /Zi /Od"; // Debug information } else if (CRenderer::CV_r_shadersdebug == 4) { debugFlags = " /Zi /O3"; // Debug information, optimized shaders } flags = AZStd::move(AZStd::string::format("/nologo /E %%s /T %%s /Zpr /Gec %s %s /Fo \"%%s\" \"%%s\"", extraFlags, debugFlags)); } break; // ---------------------------------------- case eSC_GLSL_HLSLcc: { // Translate flags for HLSLCrossCompiler compiler. All flags come from 'Code\Tools\HLSLCrossCompiler\include\hlslcc.h' unsigned int translateFlags = 0x1 | // Each constant buffer will have its own uniform block 0x100 | // Invert clip space position Y 0x200 | // Convert clip sapce position Z 0x400 | // Avoid resource bindings and locations 0x800 | // Do not use an array for temporary registers 0x8000 | // Do not add GLSL version macro 0x10000; // Avoid shader image load store extension EShaderLanguage shaderLanguage = GetShaderLanguage(); switch (shaderLanguage) { case eSL_GL4_1: case eSL_GL4_4: { const char* glVer = (shaderLanguage == eSL_GL4_1) ? "410" : "440"; flags = AZStd::move(AZStd::string::format("-lang=%s -flags=%u -fxc=\"%%s /nologo /E %%s /T %%s /Zpr /Gec /Fo\" -out=\"%%s\" -in=\"%%s\"", glVer, translateFlags)); } break; case eSL_GLES3_0: { translateFlags |= 0x20000 | // Syntactic workarounds for driver bugs found in Qualcomm devices running OpenGL ES 3.0 0x40000; // Add half support flags = AZStd::move(AZStd::string::format("-lang=es300 -flags=%u -fxc=\"%%s /nologo /E %%s /T %%s /Zpr /Gec /Fo\" -out=\"%%s\" -in=\"%%s\"", translateFlags)); } break; case eSL_GLES3_1: { translateFlags |= 0x40000; // Add half support flags = AZStd::move(AZStd::string::format("-lang=es310 -flags=%u -fxc=\"%%s /nologo /E %%s /T %%s /Zpr /Gec /Fo\" -out=\"%%s\" -in=\"%%s\"", translateFlags)); } break; default: AZ_Assert(false, "Non-GLSL shader language used with the GLSL HLSLcc compiler."); break; } } break; // ---------------------------------------- case eSC_METAL_HLSLcc: { // Translate flags for HLSLCrossCompilerMETAL compiler. All flags come from 'Code\Tools\HLSLCrossCompilerMETAL\include\hlslcc.h' unsigned int translateFlags = //0x40000 |// Add half support 0x1 | // Each constant buffer will have its own uniform block 0x100 | // Declare inputs and outputs with their semantic name appended 0x200 | // Combine texture/sampler pairs used together into samplers named "texturename_X_samplernamC" 0x400 | // Attribute and uniform explicit location qualifiers are disabled (even if the language version supports that) 0x800; // Global uniforms are not stored in a struct flags = AZStd::move(AZStd::string::format("-lang=metal -flags=%u -fxc=\"%%s /nologo /E %%s /T %%s /Zpr /Gec /Fo\" -out=\"%%s\" -in=\"%%s\"", translateFlags)); } break; // ---------------------------------------- case eSC_GLSL_LLVM_DXC: { // Translate flags for DirectXShaderCompiler GLSL compiler. All flags come from 'DirectXShaderCompiler\src\tools\clang\tools\dxcGL\HLSLCrossCompiler\include\hlslcc.h' unsigned int translateFlags = 0x1 | // Each constant buffer will have its own uniform block 0x100 | // Invert clip space position Y 0x200 | // Convert clip sapce position Z 0x400 | // Avoid resource bindings and locations 0x800 | // Do not use an array for temporary registers 0x8000 | // Do not add GLSL version macro 0x10000| // Avoid shader image load store extension 0x20000; // Declare dynamically indexed constant buffers as an array of floats EShaderLanguage shaderLanguage = GetShaderLanguage(); switch (shaderLanguage) { case eSL_GL4_1: case eSL_GL4_4: { const char* glVer = (shaderLanguage == eSL_GL4_1) ? "410" : "440"; flags = AZStd::move(AZStd::string::format("-translate_flags %u -translate %s -E %%s -T %%s -Zpr -not_use_legacy_cbuf_load -Gfa -Fo \"%%s\" \"%%s\"", translateFlags, glVer)); } break; case eSL_GLES3_0: case eSL_GLES3_1: { const char* glesVer = (shaderLanguage == eSL_GLES3_0) ? "es300" : "es310"; flags = AZStd::move(AZStd::string::format("-translate_flags %u -translate %s -E %%s -T %%s -Zpr -not_use_legacy_cbuf_load -Gfa -Fo \"%%s\" \"%%s\"", translateFlags, glesVer)); } break; default: AZ_Assert(false, "Non-GLSL shader language used with the LLVM DXC compiler."); break; } } break; // ---------------------------------------- case eSC_METAL_LLVM_DXC: { // Translate flags for DirectXShaderCompiler Metal compiler. All flags come from 'DirectXShaderCompiler\src\tools\clang\tools\dxcMetal\HLSLCrossCompilerMETAL\include\hlslcc.h' unsigned int translateFlags = 0x1 | // Each constant buffer will have its own uniform block 0x100 | // Declare inputs and outputs with their semantic name appended 0x200 | // Combine texture/sampler pairs used together into samplers named "texturename_X_samplername" 0x400 | // Attribute and uniform explicit location qualifiers are disabled (even if the language version supports that) 0x800 | // Global uniforms are not stored in a struct 0x2000; // Do not use an array for temporary registers #if defined(AZ_PLATFORM_MAC) translateFlags |= 0x1000; // Declare dynamically indexed constant buffers as an array of floats #endif flags = AZStd::move(AZStd::string::format("-translate_flags %u -translate metal -E %%s -T %%s -Zpr -not_use_legacy_cbuf_load -Gfa -Fo \"%%s\" \"%%s\"", translateFlags)); } break; // ---------------------------------------- case eSC_Unknown: default: AZ_Assert(false, "Unknown shader compiler"); break; } return flags; } string CShaderSrv::CreateXMLNode(const string& rTag, const string& rValue) const { string Tag = rTag; Tag += "=\""; Tag += rValue; Tag += "\" "; return Tag; } string CShaderSrv::TransformToXML(const string& rIn) const { string Out; for (size_t a = 0, Size = rIn.size(); a < Size; a++) { const char C = rIn.c_str()[a]; if (C == '&') { Out += "&"; } else if (C == '<') { Out += "<"; } else if (C == '>') { Out += ">"; } else if (C == '\"') { Out += """; } else if (C == '\'') { Out += "'"; } else { Out += C; } } return Out; } bool CShaderSrv::CreateRequest(std::vector& rVec, std::vector >& rNodes) const { string Request = "(Request.c_str(), &Request.c_str()[Request.size() + 1]); return true; } bool CShaderSrv::RequestLine(const SCacheCombination& cmb, const string& rLine) const { const string List(string(GetShaderLanguageName()) + "/" + cmb.Name.c_str() + "ShaderList.txt"); return RequestLine(List, rLine); } bool CShaderSrv::CommitPLCombinations(std::vector& rVec) { const uint32 STEPSIZE = 32; float T0 = iTimer->GetAsyncCurTime(); for (uint32 i = 0; i < rVec.size(); i += STEPSIZE) { string Line; string levelRequest; levelRequest.Format("<%d>%s", rVec[i].nCount, rVec[i].CacheName.c_str()); Line = levelRequest; for (uint32 j = 1; j < STEPSIZE && i + j < rVec.size(); j++) { Line += string(";"); levelRequest.Format("<%d>%s", rVec[i + j].nCount, rVec[i + j].CacheName.c_str()); Line += levelRequest; } if (!RequestLine(rVec[i], Line)) { return false; } } float T1 = iTimer->GetAsyncCurTime(); if(VerboseLogging()) { AZ_TracePrintf("RemoteCompiler", "CShaderSrv::CommitPLCombinations() : %3.3f to commit %" PRISIZE_T " Combinations\n", T1 - T0, rVec.size()); } return true; } EServerError CShaderSrv::Compile(std::vector& rVec, const char* pProfile, const char* pProgram, const char* pEntry, const char* pCompileFlags, const char* pIdent) const { std::vector CompileData; std::vector > Nodes; Nodes.push_back(std::pair(string("JobType"), string("Compile"))); Nodes.push_back(std::pair(string("Profile"), string(pProfile))); Nodes.push_back(std::pair(string("Program"), string(pProgram))); Nodes.push_back(std::pair(string("Entry"), string(pEntry))); Nodes.push_back(std::pair(string("CompileFlags"), string(pCompileFlags))); #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_4 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/RemoteCompiler_cpp_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/RemoteCompiler_cpp_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/RemoteCompiler_cpp_salem.inl" #endif #endif // Any fields coming after "HashStop" will not contribute to the hash calculated on the Remote Shader Compiler Server for its local cache. Nodes.push_back(std::pair(string("HashStop"), string("1"))); Nodes.push_back(std::pair(string("ShaderRequest"), string(pIdent))); Nodes.push_back(std::pair(string("Project"), string(m_RequestLineRootFolder.c_str()))); Nodes.push_back(std::pair(string("Platform"), string(GetPlatformName()))); Nodes.push_back(std::pair(string("Compiler"), string(GetShaderCompilerName()))); Nodes.push_back(std::pair(string("Language"), string(GetShaderLanguageName()))); if (gRenDev->CV_r_ShaderEmailTags && gRenDev->CV_r_ShaderEmailTags->GetString() && strlen(gRenDev->CV_r_ShaderEmailTags->GetString()) > 0) { Nodes.push_back(std::pair(string("Tags"), string(gRenDev->CV_r_ShaderEmailTags->GetString()))); } if (gRenDev->CV_r_ShaderEmailCCs && gRenDev->CV_r_ShaderEmailCCs->GetString() && strlen(gRenDev->CV_r_ShaderEmailCCs->GetString()) > 0) { Nodes.push_back(std::pair(string("EmailCCs"), string(gRenDev->CV_r_ShaderEmailCCs->GetString()))); } if (gRenDev->CV_r_ShaderCompilerDontCache) { Nodes.push_back(std::pair(string("Caching"), string("0"))); } //Nodes.push_back(std::pair(string("ShaderRequest",string(pShaderRequestLine))); EServerError errCompile = ESOK; int nRetries = 3; do { if (errCompile != ESOK) { Sleep(5000); } if (!CreateRequest(CompileData, Nodes)) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Compile() : failed composing Request XML\n"); VerboseLogging(true); return ESFailed; } errCompile = Send(CompileData); } while (errCompile == ESRecvFailed && nRetries-- > 0); rVec = CompileData; if (errCompile != ESOK) { bool logError = true; const char* why = ""; switch (errCompile) { case ESNetworkError: why = "Network Error"; break; case ESSendFailed: why = "Send Failed"; break; case ESRecvFailed: why = "Receive Failed"; break; case ESInvalidState: why = "Invalid Return State (compile issue ?!?)"; break; case ESCompileError: logError = false; why = ""; break; case ESFailed: why = ""; break; } if (logError) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Compile() : failed to compile %s (%s)", pEntry, why); VerboseLogging(true); } } return errCompile; } EServerError CShaderSrv::GetShaderList(std::vector& rVec) const { std::vector GetShaderListData; std::vector > Nodes; Nodes.push_back(std::pair(string("JobType"), string("GetShaderList"))); Nodes.push_back(std::pair(string("Project"), string(m_RequestLineRootFolder.c_str()))); Nodes.push_back(std::pair(string("Platform"), string(GetPlatformName()))); Nodes.push_back(std::pair(string("Compiler"), string(GetShaderCompilerName()))); Nodes.push_back(std::pair(string("Language"), string(GetShaderLanguageName()))); Nodes.push_back(std::pair(string("ShaderList"), string(GetShaderListFilename().c_str()))); #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_4 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/RemoteCompiler_cpp_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/RemoteCompiler_cpp_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/RemoteCompiler_cpp_salem.inl" #endif #endif EServerError errShaderGetList = ESOK; int nRetries = 3; do { if (errShaderGetList != ESOK) { Sleep(5000); } if (!CreateRequest(GetShaderListData, Nodes)) { AZ_Error("RemoteCompler", false, "ERROR: CShaderSrv::GetShaderList(): failed composing Request XML\n"); VerboseLogging(true); return ESFailed; } errShaderGetList = Send(GetShaderListData); } while (errShaderGetList == ESRecvFailed && nRetries-- > 0); rVec = GetShaderListData; if (errShaderGetList != ESOK) { bool logError = true; const char* why = ""; switch (errShaderGetList) { case ESNetworkError: why = "Network Error"; break; case ESSendFailed: why = "Send Failed"; break; case ESRecvFailed: why = "Receive Failed"; break; case ESInvalidState: why = "Invalid Return State (compile issue ?!?)"; break; case ESFailed: why = ""; break; } if (logError) { AZ_Error("RemoteCompiler", false, "ERROR: CShaderSrv::GetShaderList(): failed to get shader list (%s)", why); VerboseLogging(true); } } return errShaderGetList; } bool CShaderSrv::RequestLine(const string& rList, const string& rString) const { if (!gRenDev->CV_r_shaderssubmitrequestline) { return true; } std::vector CompileData; std::vector > Nodes; #if defined(AZ_RESTRICTED_PLATFORM) #define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_4 #if defined(AZ_PLATFORM_XENIA) #include "Xenia/RemoteCompiler_cpp_xenia.inl" #elif defined(AZ_PLATFORM_PROVO) #include "Provo/RemoteCompiler_cpp_provo.inl" #elif defined(AZ_PLATFORM_SALEM) #include "Salem/RemoteCompiler_cpp_salem.inl" #endif #endif Nodes.push_back(std::pair(string("JobType"), string("RequestLine"))); Nodes.push_back(std::pair(string("ShaderRequest"), rString)); Nodes.push_back(std::pair(string("Project"), m_RequestLineRootFolder)); Nodes.push_back(std::pair(string("Platform"), string(GetPlatformName()))); Nodes.push_back(std::pair(string("Compiler"), string(GetShaderCompilerName()))); Nodes.push_back(std::pair(string("Language"), string(GetShaderLanguageName()))); Nodes.push_back(std::pair(string("ShaderList"), rList)); if (!CreateRequest(CompileData, Nodes)) { AZ_Error("RemoteCompiler", false, "CShaderSrv::RequestLine() : failed composing Request XML\n"); VerboseLogging(true); return false; } return (Send(CompileData) == ESOK); } bool CShaderSrv::Send(AZSOCKET Socket, const char* pBuffer, uint32 Size) const { //size_t w; size_t wTotal = 0; while (wTotal < Size) { int result = AZ::AzSock::Send(Socket, pBuffer + wTotal, Size - wTotal, 0); if (AZ::AzSock::SocketErrorOccured(result)) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Send() : failed (%s)\n", AZ::AzSock::GetStringForError(result)); VerboseLogging(true); return false; } wTotal += (size_t)result; } return true; } bool CShaderSrv::Send(AZSOCKET Socket, std::vector& rCompileData) const { const uint64 Size = static_cast(rCompileData.size()); if(Size == 0) { return Send(Socket, (const char*) &Size, sizeof(Size));//send 0... if Size is 0 then (const char*)&rCompileData[0] cannot be accessed. } return Send(Socket, (const char*)&Size, sizeof(Size)) && Send(Socket, (const char*)&rCompileData[0], static_cast(Size)); } #define MAX_TIME_TO_WAIT 100000 EServerError CShaderSrv::Recv(AZSOCKET Socket, std::vector& rCompileData) const { const size_t Offset = 5;//version 2 has 4byte size and 1 byte state // const uint32 Size = static_cast(rCompileData.size()); // return Send(Socket,(const char*)&Size,4) || // Send(Socket,(const char*)&rCompileData[0],Size); // delete[] optionsBuffer; uint32 nMsgLength = 0; uint32 nTotalRecived = 0; const size_t BLOCKSIZE = 4 * 1024; const size_t SIZELIMIT = 1024 * 1024; rCompileData.resize(0); rCompileData.reserve(64 * 1024); int CurrentPos = 0; while (rCompileData.size() < SIZELIMIT) { rCompileData.resize(CurrentPos + BLOCKSIZE); int Recived = SOCKET_ERROR; int waitingtime = 0; while (Recived < 0) { Recived = AZ::AzSock::Recv(Socket, reinterpret_cast(&rCompileData[CurrentPos]), BLOCKSIZE, 0); if (AZ::AzSock::SocketErrorOccured(Recived)) { AZ::AzSock::AzSockError error = AZ::AzSock::AzSockError(Recived); if (error == AZ::AzSock::AzSockError::eASE_EWOULDBLOCK) { // are we out of time if (waitingtime > MAX_TIME_TO_WAIT) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Recv() : Out of time during waiting %d seconds on block, sys_net_errno=%s\n", MAX_TIME_TO_WAIT, AZ::AzSock::GetStringForError(Recived)); VerboseLogging(true); return ESRecvFailed; } waitingtime += 5; // sleep a bit and try again Sleep(5); } else { // count on retry to fix this after a small sleep AZ_Error("RemoteCompiler", false, "CShaderSrv::Recv() : at offset %lu: sys_net_errno=%s\n", (unsigned long)rCompileData.size(), AZ::AzSock::GetStringForError(Recived)); VerboseLogging(true); return ESRecvFailed; } } } if (Recived >= 0) { nTotalRecived += Recived; } if (nTotalRecived >= 4) { nMsgLength = *(uint32*)&rCompileData[0] + Offset; } if (Recived == 0 || nTotalRecived == nMsgLength) { rCompileData.resize(nTotalRecived); break; } CurrentPos += Recived; } return ProcessResponse(rCompileData); } // given a data vector, check to see if its an error or a success situation. // if its an error, replace the buffer with the uncompressed error string if possible. EServerError CShaderSrv::ProcessResponse(std::vector& rCompileData) const { // so internally the message is like this // [payload length 4 bytes] [status 1 byte] [payload] // note that the length of the payload is given, not the total length of the message // which is actually payload length + [4 bytes + 1 byte status] const size_t OffsetToPayload = sizeof(unsigned int) + sizeof(uint8); // probably 5 bytes if (rCompileData.size() < OffsetToPayload) { AZ_Error("RemoteCompiler", false, "CShaderSrv::ProcessResponse() : data incomplete from server (only %i bytes received)\n", static_cast(rCompileData.size())); VerboseLogging(true); rCompileData.clear(); return ESRecvFailed; } uint32 payloadSize = *(uint32*)&rCompileData[0]; uint8 state = rCompileData[4]; if (payloadSize + OffsetToPayload != rCompileData.size()) { AZ_Error("RemoteCompiler", false, "CShaderSrv::ProcessResponse() : data incomplete from server - expected %i bytes, got %i bytes\n", static_cast(payloadSize + OffsetToPayload), static_cast(rCompileData.size())); VerboseLogging(true); rCompileData.clear(); return ESRecvFailed; } if (rCompileData.size() > OffsetToPayload) // ie, not a zero-byte payload { // move the payload to the beginning of the array, so that the first byte is the first byte of the payload memmove(&rCompileData[0], &rCompileData[OffsetToPayload], rCompileData.size() - OffsetToPayload); rCompileData.resize(rCompileData.size() - OffsetToPayload); } else { rCompileData.clear(); } // decompress datablock if available. if (rCompileData.size() > sizeof(unsigned int)) // theres a datablock, which is compressed and there's enough data in there for a header of 4 bytes and payload { // [4 bytes - size of uncompressed message in network order][compressed payload message] // Decompress incoming payload std::vector rCompressedData; rCompressedData.swap(rCompileData); uint32 nSrcUncompressedLen = *(uint32*)&rCompressedData[0]; SwapEndian(nSrcUncompressedLen); size_t nUncompressedLen = (size_t)nSrcUncompressedLen; // Maximum size allowed for a shader in bytes static const size_t maxShaderSize = 10ull * (1024ull * 1024ull); // 10 MB if (nUncompressedLen > maxShaderSize) { // Shader too big, something is wrong. rCompileData.clear(); // don't propogate "something is wrong" data return ESFailed; } rCompileData.resize(nUncompressedLen); if (nUncompressedLen > 0) { if (!gEnv->pSystem->DecompressDataBlock(&rCompressedData[4], rCompressedData.size() - 4, &rCompileData[0], nUncompressedLen)) { rCompileData.clear(); // don't propogate corrupted data return ESFailed; } } } if (state != 1) //1==ECSJS_DONE state on server, dont change! { // getting here means SOME sort of error occurred. // don't print compile errors here, they'll be handled later if (state == 5) //5==ECSJS_COMPILE_ERROR state on server, dont change! { return ESCompileError; } AZ_Error("RemoteCompiler", false, "CShaderSrv::ProcessResponse() : data contains invalid return status: state = %d \n", state); VerboseLogging(true); return ESInvalidState; } return ESOK; } void CShaderSrv::Tokenize(tdEntryVec& rRet, const string& Tokens, const string& Separator) const { rRet.clear(); string::size_type Pt; string::size_type Start = 0; string::size_type SSize = Separator.size(); while ((Pt = Tokens.find(Separator, Start)) != string::npos) { string SubStr = Tokens.substr(Start, Pt - Start); rRet.push_back(SubStr); Start = Pt + SSize; } rRet.push_back(Tokens.substr(Start)); } EServerError CShaderSrv::Send(std::vector& rCompileData) const { if (rCompileData.size() > std::numeric_limits::max()) { AZ_Error("RemoteCompiler", false, "CShaderSrv::Send() : compile data too big to send.\n"); VerboseLogging(true); return ESFailed; } // this function expects to block until a response is received or failure occurs. AzFramework::SocketConnection* engineConnection = AzFramework::SocketConnection::GetInstance(); bool useAssetProcessor = ((CRenderer::CV_r_AssetProcessorShaderCompiler != 0) && (engineConnection) && (engineConnection->IsConnected())); #if defined(AZ_TESTS_ENABLED) if (m_unitTestMode) { useAssetProcessor = true; // always test asset processor-based code } #endif // AZ_TESTS_ENABLED if (useAssetProcessor) { EServerError resultFromConnection = SendRequestViaEngineConnection(rCompileData); if (resultFromConnection != ESOK) { return resultFromConnection; } } else { EServerError resultFromSocket = SendRequestViaSocket(rCompileData); if (resultFromSocket != ESOK) { return resultFromSocket; } } if (rCompileData.size() < 4) { return ESFailed; } return ESOK; } EServerError CShaderSrv::SendRequestViaSocket(std::vector& rCompileData) const { AZSOCKET Socket = AZ_SOCKET_INVALID; int Err = SOCKET_ERROR; // generate the list of servers to make the request to: tdEntryVec ServerVec; if (gEnv->pConsole->GetCVar("r_ShaderCompilerServer")) { Tokenize(ServerVec, gEnv->pConsole->GetCVar("r_ShaderCompilerServer")->GetString(), ","); } if (ServerVec.empty()) { ServerVec.push_back("localhost"); } if(VerboseLogging()) { AZ_TracePrintf("RemoteCompler", "INFO: CShaderSrv::SendRequestViaSocket(): connect to remote shader compiler server: %s...\n", gRenDev->CV_r_ShaderCompilerServer->GetString()); } //connect //try each entry in the list from front to back bool didconnect = false; bool sent = false; bool received = false; for (uint32 nServer = 0; nServer < ServerVec.size(); nServer++) { string Server = ServerVec[nServer]; //try 3 times each in turn for (uint32 nRetries = 0; nRetries < 3; nRetries++) { if(nRetries) { AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): retry % i to connect to: %s...\n", nRetries, Server.c_str()); VerboseLogging(true); } else { if(VerboseLogging()) { AZ_TracePrintf("RemoteCompler", "INFO: CShaderSrv::SendRequestViaSocket(): connect to: %s...\n", Server.c_str()); } } //create the socket Socket = AZ::AzSock::Socket(); //if anything went wrong creating the socket, this was not a valid try, so try again if (!AZ::AzSock::IsAzSocketValid(Socket)) { if (nRetries) { nRetries--; } AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): can't create client socket: error %s\n", AZ::AzSock::GetStringForError(Socket)); VerboseLogging(true); } else { //we have a socket, try to connect AZ::AzSock::SetSocketOption(Socket, AZ::AzSock::AzSocketOption::REUSEADDR, true); AZ::AzSock::AzSocketAddress socketAddress; socketAddress.SetAddress(Server.c_str(), gRenDev->CV_r_ShaderCompilerPort); Err = AZ::AzSock::Connect(Socket, socketAddress); if (AZ::AzSock::SocketErrorOccured(Err)) { //connect failed, see if it failed because we don't have enough buffer, if so its not a legit fail of this server, retry // if buffer is full try sleeping a bit before retrying // (if you keep getting this issue then try using same shutdown mechanism as server is doing (see server code)) // (for more info on windows side check : http://www.proxyplus.cz/faq/articles/EN/art10002.htm) if (Err == static_cast(AZ::AzSock::AzSockError::eASE_ENOBUFS)) { AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): ENOBUFS: the buffer is full, try again in 5 seconds. %s (sys_net_errno=%s, retrying %d)\n", Server.c_str(), AZ::AzSock::GetStringForError(Err), nRetries); VerboseLogging(true); if (nRetries) { nRetries--; } //wait 5 seconds before retry Sleep(5000); } else { //legit fail to connect, retry AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): could not connect to %s (sys_net_errno=%s, retrying %d)\n", Server.c_str(), AZ::AzSock::GetStringForError(Err), nRetries); VerboseLogging(true); //wait 1 second before retry Sleep(1000); } //close the socket for a retry AZ::AzSock::CloseSocket(Socket); Socket = AZ_SOCKET_INVALID; } else { if(VerboseLogging()) { AZ_TracePrintf("RemoteCompiler", "INFO: CShaderSrv::SendRequestViaSocket(): connected to: %s...\n", Server.c_str()); } didconnect = true; //we connected, send if (!Send(Socket, rCompileData)) { //send failed AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): failed to send: sys_net_errno=%s\n", AZ::AzSock::GetStringForError(Err)); VerboseLogging(true); //wait 1 second before retry Sleep(1000); AZ::AzSock::CloseSocket(Socket); Socket = AZ_SOCKET_INVALID; } else { sent = true; //send succeeded, wait for recv EServerError Error = Recv(Socket, rCompileData); if (Error != ESOK) { //recv failed AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): failed to recv: EServerError=%i\n", Error); VerboseLogging(true); //wait 1 second before retry Sleep(1000); AZ::AzSock::CloseSocket(Socket); Socket = AZ_SOCKET_INVALID; } else { received = true; //we are done, it succeeded //shutdown the client side of the socket because we are done listening if(VerboseLogging()) { AZ_TracePrintf("RemoteCompler", "INFO: CShaderSrv::SendRequestViaSocket(): shader request succeeded.\n"); } Err = AZ::AzSock::Shutdown(Socket, SD_BOTH); if (Err == SOCKET_ERROR) { AZ_Warning("RemoteCompiler", false, "WARN: CShaderSrv::SendRequestViaSocket(): succeeded but and got error shutting down socket: sys_net_errno=%s\n", AZ::AzSock::GetStringForError(Err)); VerboseLogging(true); } else { //put this in the else because OSX can have a problem calling closesocket on a failed shutdown of a socket AZ::AzSock::CloseSocket(Socket); } Socket = AZ_SOCKET_INVALID; return ESOK; } } } } } } //we failed AZ::AzSock::CloseSocket(Socket); Socket = AZ_SOCKET_INVALID; rCompileData.resize(0); if (didconnect) { const AZStd::string title = "Remote Shader Compiler"; const AZStd::string message = AZStd::string::format("We connected to the server but failed to compile the shader!"); AZ_Error("RemoteCompiler", false, "ERROR: CShaderSrv::SendRequestViaSocket(): %s\n", message.c_str()); VerboseLogging(true); if(!gEnv->IsInToolMode()) { EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, title, message, false); } } else { const AZStd::string title = "Remote Shader Compiler"; const AZStd::string message = AZStd::string::format("Unable to connect to Remote Shader Compiler at %s", gRenDev->CV_r_ShaderCompilerServer->GetString()); AZ_Error("RemoteCompiler", false, "ERROR: CShaderSrv::SendRequestViaSocket(): %s\n", message.c_str()); VerboseLogging(true); if(!gEnv->IsInToolMode()) { AZStd::vector options; options.push_back("OK"); EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayBlockingDialog, title, message, options); } } return ESNetworkError; } bool CShaderSrv::EncapsulateRequestInEngineConnectionProtocol(std::vector& rCompileData) const { if (rCompileData.empty()) { AZ_Error("RemoteCompiler", false, "CShaderSrv::EncapsulateRequestInEngineConnectionProtocol() : Engine Connection was unable to send the message - zero bytes size."); VerboseLogging(true); return false; } string serverList = gEnv->pConsole->GetCVar("r_ShaderCompilerServer")->GetString(); unsigned int serverListLength = static_cast(serverList.size()); unsigned short serverPort = static_cast(gEnv->pConsole->GetCVar("r_ShaderCompilerPort")->GetIVal()); if (serverListLength == 0) { AZ_Error("RemoteCompiler", false, "r_ShaderCompilerServer cvar is empty - no servers to send to. This CVAR should contain the list of servers to send shader compiler requests to."); return false; } // we're packing at the end because sometimes, you don't need to copy the data in that case. std::size_t originalSize = rCompileData.size(); // a null the string a null the port the length of the string rCompileData.resize(originalSize + 1 + serverListLength + 1 + sizeof(serverPort) + sizeof(unsigned int)); uint8* dataStart = rCompileData.data() + originalSize; *dataStart = 0; // null ++dataStart; memcpy(dataStart, serverList.c_str(), serverList.size()); // server list data dataStart += serverList.size(); *dataStart = 0; // null ++dataStart; SwapEndian(serverPort); SwapEndian(serverListLength); memcpy(dataStart, &serverPort, sizeof(serverPort)); // server port dataStart += sizeof(serverPort); memcpy(dataStart, &serverListLength, sizeof(serverListLength)); // server list length dataStart += sizeof(serverListLength); // check for buffer overrun assert(reinterpret_cast(dataStart) - reinterpret_cast(rCompileData.data()) == rCompileData.size()); return true; } EServerError CShaderSrv::SendRequestViaEngineConnection(std::vector& rCompileData) const { // use the asset processor instead of direct socket. // wrap it up in a protocol structure - very straight forward - the requestID followed by the data // the protocol already takes care of the data size, underneath, so no need to send that // what we need include the information about what server(s) to connect to. // we can append to the end of the compile data so as to avoid copying unless we need to if (!EncapsulateRequestInEngineConnectionProtocol(rCompileData)) { return ESFailed; } if (!m_remoteState->SubmitRequestAndBlockForResponse(rCompileData)) { rCompileData.clear(); AZ_Error("RemoteCompiler", false, "CShaderSrv::SendRequestViaEngineConnection() : Engine Connection was unable to send the message."); VerboseLogging(true); return ESNetworkError; } if (rCompileData.empty()) { AZ_Error("RemoteCompiler", false, "CShaderSrv::SendRequestViaEngineConnection() : Recv data empty from server (didn't receive anything)\n"); VerboseLogging(true); const AZStd::string title = "Remote Shader Compiler"; const AZStd::string message = "Unable to connect to Remote Shader Compiler"; if(!gEnv->IsInToolMode()) { EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayOkDialog, title, message, false); } return ESRecvFailed; } // Check for error embedded in the response! return ProcessResponse(rCompileData); } #if defined(AZ_TESTS_ENABLED) void CShaderSrv::EnableUnitTestingMode(bool mode) { m_unitTestMode = mode; m_remoteState->SetUnitTestMode(mode); } #endif // AZ_TESTS_ENABLED } // end namespace