You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1483 lines
59 KiB
C++
1483 lines
59 KiB
C++
/*
|
|
* 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 "RenderDll_precompiled.h"
|
|
#include "RemoteCompiler.h"
|
|
#include "../RenderCapabilities.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
#include <AzCore/Socket/AzSocket.h>
|
|
#include <AzCore/NativeUI/NativeUIRequests.h>
|
|
#include <AzCore/PlatformId/PlatformId.h>
|
|
#include <AzCore/Utils/Utils.h>
|
|
#include <AzFramework/Network/SocketConnection.h>
|
|
#include <AzFramework/Asset/AssetSystemTypes.h>
|
|
|
|
#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
|
|
#include AZ_RESTRICTED_FILE(RemoteCompiler_cpp)
|
|
#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<void(const void* payload, unsigned int payloadSize)> TResponseCallback;
|
|
|
|
RemoteProxyState()
|
|
{
|
|
m_engineConnectionCallbackInstalled = false;
|
|
m_unitTestMode = false;
|
|
m_remoteRequestCRC = AZ_CRC("ShaderCompilerProxyRequest");
|
|
m_remoteResponseCRC = AZ_CRC("ShaderCompilerProxyResponse");
|
|
m_nextAssignedToken = 0;
|
|
}
|
|
|
|
void SetUnitTestMode(bool newMode)
|
|
{
|
|
m_unitTestMode = newMode;
|
|
}
|
|
|
|
bool SubmitRequestAndBlockForResponse(std::vector<uint8>& 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<CryMutex> 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 (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<uint8> 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<char> 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.
|
|
{
|
|
// 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<CryMutex> 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<unsigned int, TResponseCallback> m_responsesAwaitingCallback;
|
|
|
|
void OnReceiveRemoteResponse([[maybe_unused]] 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<const uint8*>(payload);
|
|
const uint8* end_payload = payload_start + payloadSize;
|
|
const uint8* messageIDPtr = end_payload - 4;
|
|
|
|
unsigned int responseId = *reinterpret_cast<const unsigned int*>(messageIDPtr);
|
|
SwapEndian(responseId);
|
|
|
|
CryAutoLock<CryMutex> 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()
|
|
{
|
|
m_unitTestMode = false;
|
|
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 = "";
|
|
|
|
auto projectName = AZ::Utils::GetProjectName();
|
|
ICVar* pCompilerFolderSuffix = CRenderer::CV_r_ShaderCompilerFolderSuffix;
|
|
|
|
if (!projectName.empty())
|
|
{
|
|
if (pCompilerFolderSuffix)
|
|
{
|
|
string suffix = pCompilerFolderSuffix->GetString();
|
|
suffix.Trim();
|
|
projectName.append(suffix);
|
|
}
|
|
|
|
projectName.append("/");
|
|
m_RequestLineRootFolder.assign(projectName.c_str(), projectName.size());
|
|
}
|
|
|
|
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_Jasper:
|
|
shaderCompiler = eSC_Jasper_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",
|
|
"D3D11_FXC",
|
|
"GLSL_HLSLcc",
|
|
"METAL_HLSLcc",
|
|
"GLSL_LLVM_DXC",
|
|
"METAL_LLVM_DXC",
|
|
"Jasper_FXC"
|
|
};
|
|
|
|
EShaderCompiler shaderCompiler = GetShaderCompiler();
|
|
return shaderCompilerNames[shaderCompiler];
|
|
}
|
|
|
|
const char* CShaderSrv::GetPlatformName() const
|
|
{
|
|
const char *platformName = "Unknown";
|
|
|
|
switch (CParserBin::m_targetPlatform)
|
|
{
|
|
case AZ::PlatformID::PLATFORM_WINDOWS_64:
|
|
platformName = "PC";
|
|
break;
|
|
case AZ::PlatformID::PLATFORM_ANDROID_64:
|
|
platformName = "Android";
|
|
break;
|
|
case AZ::PlatformID::PLATFORM_APPLE_OSX:
|
|
platformName = "Mac";
|
|
break;
|
|
case AZ::PlatformID::PLATFORM_APPLE_IOS:
|
|
platformName = "iOS";
|
|
break;
|
|
case AZ::PlatformID::PLATFORM_LINUX_64:
|
|
platformName = "Linux";
|
|
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::PlatformID::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
|
|
default:
|
|
AZ_Assert(false, "Unknown shader platform");
|
|
break;
|
|
}
|
|
|
|
return platformName;
|
|
}
|
|
|
|
AZStd::string CShaderSrv::GetShaderCompilerFlags([[maybe_unused]] EHWShaderClass eClass, [[maybe_unused]] UPipelineState pipelineState, [[maybe_unused]] 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
|
|
#include AZ_RESTRICTED_FILE(RemoteCompiler_cpp)
|
|
#endif
|
|
|
|
#if defined(AZ_RESTRICTED_PLATFORM)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2
|
|
#include AZ_RESTRICTED_FILE(RemoteCompiler_cpp)
|
|
#endif
|
|
|
|
#if defined(TOOLS_SUPPORT_PROVO)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2
|
|
#include AZ_RESTRICTED_FILE_EXPLICIT(Shaders/RemoteCompiler_cpp, provo)
|
|
#endif
|
|
#if defined(TOOLS_SUPPORT_JASPER)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2
|
|
#include AZ_RESTRICTED_FILE_EXPLICIT(Shaders/RemoteCompiler_cpp, jasper)
|
|
#endif
|
|
#if defined(TOOLS_SUPPORT_SALEM)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_2
|
|
#include AZ_RESTRICTED_FILE_EXPLICIT(Shaders/RemoteCompiler_cpp, salem)
|
|
#endif
|
|
}
|
|
break;
|
|
|
|
// ----------------------------------------
|
|
case eSC_Jasper_FXC:
|
|
case eSC_D3D11_FXC:
|
|
{
|
|
const char* extraFlags = "";
|
|
|
|
const char* debugFlags = "";
|
|
if IsCVarConstAccess(constexpr) (CRenderer::CV_r_shadersdebug == 3)
|
|
{
|
|
debugFlags = " /Zi /Od"; // Debug information
|
|
}
|
|
else if IsCVarConstAccess(constexpr) (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<uint8>& rVec,
|
|
std::vector<std::pair<string, string> >& rNodes) const
|
|
{
|
|
string Request = "<?xml version=\"1.0\"?><Compile ";
|
|
Request += CreateXMLNode("Version", TransformToXML("2.3"));
|
|
for (size_t a = 0; a < rNodes.size(); a++)
|
|
{
|
|
Request += CreateXMLNode(rNodes[a].first, TransformToXML(rNodes[a].second));
|
|
}
|
|
|
|
Request += " />";
|
|
rVec = std::vector<uint8>(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<SCacheCombination>& 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<uint8>& rVec,
|
|
const char* pProfile,
|
|
const char* pProgram,
|
|
const char* pEntry,
|
|
const char* pCompileFlags,
|
|
const char* pIdent) const
|
|
{
|
|
std::vector<uint8> CompileData;
|
|
std::vector<std::pair<string, string> > Nodes;
|
|
|
|
Nodes.push_back(std::pair<string, string>(string("JobType"), string("Compile")));
|
|
Nodes.push_back(std::pair<string, string>(string("Profile"), string(pProfile)));
|
|
Nodes.push_back(std::pair<string, string>(string("Program"), string(pProgram)));
|
|
Nodes.push_back(std::pair<string, string>(string("Entry"), string(pEntry)));
|
|
Nodes.push_back(std::pair<string, string>(string("CompileFlags"), string(pCompileFlags)));
|
|
|
|
#if defined(AZ_RESTRICTED_PLATFORM)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_4
|
|
#include AZ_RESTRICTED_FILE(RemoteCompiler_cpp)
|
|
#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, string>(string("HashStop"), string("1")));
|
|
Nodes.push_back(std::pair<string, string>(string("ShaderRequest"), string(pIdent)));
|
|
Nodes.push_back(std::pair<string, string>(string("Project"), string(m_RequestLineRootFolder.c_str())));
|
|
Nodes.push_back(std::pair<string, string>(string("Platform"), string(GetPlatformName())));
|
|
Nodes.push_back(std::pair<string, string>(string("Compiler"), string(GetShaderCompilerName())));
|
|
Nodes.push_back(std::pair<string, string>(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, string>(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, string>(string("EmailCCs"), string(gRenDev->CV_r_ShaderEmailCCs->GetString())));
|
|
}
|
|
|
|
if (gRenDev->CV_r_ShaderCompilerDontCache)
|
|
{
|
|
Nodes.push_back(std::pair<string, string>(string("Caching"), string("0")));
|
|
}
|
|
|
|
//Nodes.push_back(std::pair<string,string>(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<uint8>& rVec) const
|
|
{
|
|
std::vector<uint8> GetShaderListData;
|
|
std::vector<std::pair<string, string> > Nodes;
|
|
|
|
Nodes.push_back(std::pair<string, string>(string("JobType"), string("GetShaderList")));
|
|
Nodes.push_back(std::pair<string, string>(string("Project"), string(m_RequestLineRootFolder.c_str())));
|
|
Nodes.push_back(std::pair<string, string>(string("Platform"), string(GetPlatformName())));
|
|
Nodes.push_back(std::pair<string, string>(string("Compiler"), string(GetShaderCompilerName())));
|
|
Nodes.push_back(std::pair<string, string>(string("Language"), string(GetShaderLanguageName())));
|
|
Nodes.push_back(std::pair<string, string>(string("ShaderList"), string(GetShaderListFilename().c_str())));
|
|
|
|
#if defined(AZ_RESTRICTED_PLATFORM)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_4
|
|
#include AZ_RESTRICTED_FILE(RemoteCompiler_cpp)
|
|
#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<uint8> CompileData;
|
|
std::vector<std::pair<string, string> > Nodes;
|
|
|
|
#if defined(AZ_RESTRICTED_PLATFORM)
|
|
#define AZ_RESTRICTED_SECTION REMOTECOMPILER_CPP_SECTION_4
|
|
#include AZ_RESTRICTED_FILE(RemoteCompiler_cpp)
|
|
#endif
|
|
|
|
Nodes.push_back(std::pair<string, string>(string("JobType"), string("RequestLine")));
|
|
Nodes.push_back(std::pair<string, string>(string("ShaderRequest"), rString));
|
|
Nodes.push_back(std::pair<string, string>(string("Project"), m_RequestLineRootFolder));
|
|
Nodes.push_back(std::pair<string, string>(string("Platform"), string(GetPlatformName())));
|
|
Nodes.push_back(std::pair<string, string>(string("Compiler"), string(GetShaderCompilerName())));
|
|
Nodes.push_back(std::pair<string, string>(string("Language"), string(GetShaderLanguageName())));
|
|
Nodes.push_back(std::pair<string, string>(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<uint8>& rCompileData) const
|
|
{
|
|
const uint64 Size = static_cast<uint32>(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<uint32>(Size));
|
|
}
|
|
|
|
#define MAX_TIME_TO_WAIT 100000
|
|
|
|
EServerError CShaderSrv::Recv(AZSOCKET Socket, std::vector<uint8>& rCompileData) const
|
|
{
|
|
const size_t Offset = 5;//version 2 has 4byte size and 1 byte state
|
|
// const uint32 Size = static_cast<uint32>(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<char*>(&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<uint8>& 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<int>(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<int>(payloadSize + OffsetToPayload), static_cast<int>(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<uint8> 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<uint8>& rCompileData) const
|
|
{
|
|
if (rCompileData.size() > std::numeric_limits<int>::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 (m_unitTestMode)
|
|
{
|
|
useAssetProcessor = true; // always test asset processor-based code
|
|
}
|
|
|
|
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<uint8>& 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::s32>(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<AZStd::string> options;
|
|
options.push_back("OK");
|
|
EBUS_EVENT(AZ::NativeUI::NativeUIRequestBus, DisplayBlockingDialog, title, message, options);
|
|
}
|
|
}
|
|
return ESNetworkError;
|
|
}
|
|
|
|
bool CShaderSrv::EncapsulateRequestInEngineConnectionProtocol(std::vector<uint8>& 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<unsigned int>(serverList.size());
|
|
unsigned short serverPort = static_cast<unsigned short>(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<ptrdiff_t>(dataStart) - reinterpret_cast<ptrdiff_t>(rCompileData.data()) == rCompileData.size());
|
|
return true;
|
|
}
|
|
|
|
EServerError CShaderSrv::SendRequestViaEngineConnection(std::vector<uint8>& 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);
|
|
}
|
|
|
|
void CShaderSrv::EnableUnitTestingMode(bool mode)
|
|
{
|
|
m_unitTestMode = mode;
|
|
m_remoteState->SetUnitTestMode(mode);
|
|
}
|
|
|
|
} // end namespace
|
|
|