rendering: add opengl hlsl shader translation
This commit is contained in:
@@ -187,6 +187,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/RHIScreenshot.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/RHIDescriptorPool.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/RHIDescriptorSet.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/D3D12/D3D12Enums.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/D3D12/D3D12Device.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/D3D12/D3D12CommandQueue.h
|
||||
@@ -249,6 +250,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanSwapChain.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDevice.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanScreenshot.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanBuffer.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanTexture.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanSampler.cpp
|
||||
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
bool Compile(const char* vertexSource, const char* fragmentSource, const char* geometrySource);
|
||||
bool CompileCompute(const char* computeSource);
|
||||
bool Compile(const char* source, ShaderType type);
|
||||
bool AdoptProgram(unsigned int program, ShaderType type);
|
||||
|
||||
void Use() const;
|
||||
|
||||
@@ -49,4 +50,4 @@ private:
|
||||
};
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCEngine/RHI/RHITypes.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
enum class SpirvTargetEnvironment : uint8_t {
|
||||
Vulkan = 0,
|
||||
OpenGL = 1
|
||||
};
|
||||
|
||||
struct CompiledSpirvShader {
|
||||
std::vector<uint32_t> spirvWords;
|
||||
ShaderType type = ShaderType::Vertex;
|
||||
std::string entryPoint;
|
||||
};
|
||||
|
||||
bool CompileSpirvShader(const ShaderCompileDesc& desc,
|
||||
SpirvTargetEnvironment targetEnvironment,
|
||||
CompiledSpirvShader& outShader,
|
||||
std::string* errorMessage = nullptr);
|
||||
|
||||
bool TranspileSpirvToOpenGLGLSL(const CompiledSpirvShader& shader,
|
||||
std::string& outSourceText,
|
||||
std::string* errorMessage = nullptr);
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
@@ -1,19 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCEngine/RHI/RHITypes.h"
|
||||
#include "XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
struct VulkanCompiledShader {
|
||||
std::vector<uint32_t> spirvWords;
|
||||
ShaderType type = ShaderType::Vertex;
|
||||
std::string entryPoint;
|
||||
};
|
||||
using VulkanCompiledShader = CompiledSpirvShader;
|
||||
|
||||
bool CompileVulkanShader(const ShaderCompileDesc& desc,
|
||||
VulkanCompiledShader& outShader,
|
||||
|
||||
@@ -23,9 +23,14 @@
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLDescriptorPool.h"
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLDescriptorSet.h"
|
||||
#include "XCEngine/RHI/OpenGL/OpenGLPipelineLayout.h"
|
||||
#include "XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h"
|
||||
#include "XCEngine/Debug/Logger.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
|
||||
typedef const char* (WINAPI* PFNWGLGETEXTENSIONSSTRINGARBPROC)(HDC hdc);
|
||||
typedef BOOL (WINAPI* PFNWGLCHOOSEPIXELFORMATARBPROC)(HDC hdc, const int* piAttribIList, const FLOAT* pfAttribFList, UINT nMaxFormats, int* piFormats, UINT* nNumFormats);
|
||||
@@ -135,6 +140,406 @@ std::string SourceToString(const ShaderCompileDesc& desc) {
|
||||
return std::string(reinterpret_cast<const char*>(desc.source.data()), desc.source.size());
|
||||
}
|
||||
|
||||
bool LoadShaderSourceText(const ShaderCompileDesc& desc, std::string& outSourceText) {
|
||||
outSourceText.clear();
|
||||
if (!desc.source.empty()) {
|
||||
outSourceText.assign(reinterpret_cast<const char*>(desc.source.data()), desc.source.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (desc.fileName.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ifstream file(std::filesystem::path(desc.fileName), std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outSourceText.resize(static_cast<size_t>(size));
|
||||
file.seekg(0, std::ios::beg);
|
||||
return file.read(outSourceText.data(), size).good();
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromTarget(const std::wstring& profile, ShaderType& type) {
|
||||
const std::string asciiProfile = NarrowAscii(profile);
|
||||
if (asciiProfile.find("vs_") != std::string::npos || asciiProfile == "vs") {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
if (asciiProfile.find("ps_") != std::string::npos ||
|
||||
asciiProfile.find("fs_") != std::string::npos ||
|
||||
asciiProfile == "ps" ||
|
||||
asciiProfile == "fs") {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
if (asciiProfile.find("gs_") != std::string::npos || asciiProfile == "gs") {
|
||||
type = ShaderType::Geometry;
|
||||
return true;
|
||||
}
|
||||
if (asciiProfile.find("hs_") != std::string::npos || asciiProfile == "hs") {
|
||||
type = ShaderType::TessControl;
|
||||
return true;
|
||||
}
|
||||
if (asciiProfile.find("ds_") != std::string::npos || asciiProfile == "ds") {
|
||||
type = ShaderType::TessEvaluation;
|
||||
return true;
|
||||
}
|
||||
if (asciiProfile.find("cs_") != std::string::npos || asciiProfile == "cs") {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromPath(const std::wstring& fileName, ShaderType& type) {
|
||||
if (fileName.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string extension = std::filesystem::path(fileName).extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
|
||||
if (extension == ".vert" || extension == ".vs") {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".frag" || extension == ".fs" || extension == ".ps") {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".geom" || extension == ".gs") {
|
||||
type = ShaderType::Geometry;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".tesc" || extension == ".hs") {
|
||||
type = ShaderType::TessControl;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".tese" || extension == ".ds") {
|
||||
type = ShaderType::TessEvaluation;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".comp" || extension == ".cs") {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool UsesSpirvCompilationPath(const ShaderCompileDesc& desc) {
|
||||
if (desc.sourceLanguage == ShaderLanguage::HLSL || desc.sourceLanguage == ShaderLanguage::SPIRV) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (desc.fileName.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string extension = std::filesystem::path(desc.fileName).extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return extension == ".hlsl" || extension == ".spv";
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromSource(const std::string& sourceText, ShaderType& type) {
|
||||
const std::string lowered = ToLowerAscii(sourceText);
|
||||
if (lowered.find("layout(local_size_x") != std::string::npos ||
|
||||
lowered.find("gl_globalinvocationid") != std::string::npos ||
|
||||
lowered.find("gl_localinvocationid") != std::string::npos ||
|
||||
lowered.find("gl_workgroupid") != std::string::npos) {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("emitvertex") != std::string::npos ||
|
||||
lowered.find("endprimitive") != std::string::npos ||
|
||||
lowered.find("gl_in[") != std::string::npos) {
|
||||
type = ShaderType::Geometry;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("layout(vertices") != std::string::npos ||
|
||||
lowered.find("gl_invocationid") != std::string::npos) {
|
||||
type = ShaderType::TessControl;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_tesscoord") != std::string::npos ||
|
||||
lowered.find("gl_tesslevelouter") != std::string::npos) {
|
||||
type = ShaderType::TessEvaluation;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_position") != std::string::npos) {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_fragcoord") != std::string::npos ||
|
||||
lowered.find("gl_fragcolor") != std::string::npos ||
|
||||
lowered.find("gl_fragdata") != std::string::npos ||
|
||||
lowered.find("fragcolor") != std::string::npos) {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveOpenGLShaderType(const ShaderCompileDesc& desc, const std::string& sourceText, ShaderType& type) {
|
||||
if (TryResolveShaderTypeFromTarget(desc.profile, type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (TryResolveShaderTypeFromPath(desc.fileName, type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryResolveShaderTypeFromSource(sourceText, type);
|
||||
}
|
||||
|
||||
const char* ShaderStageLabel(ShaderType type) {
|
||||
switch (type) {
|
||||
case ShaderType::Vertex: return "VERTEX";
|
||||
case ShaderType::Fragment: return "FRAGMENT";
|
||||
case ShaderType::Geometry: return "GEOMETRY";
|
||||
case ShaderType::Compute: return "COMPUTE";
|
||||
case ShaderType::TessControl:
|
||||
case ShaderType::Hull:
|
||||
return "TESS_CONTROL";
|
||||
case ShaderType::TessEvaluation:
|
||||
case ShaderType::Domain:
|
||||
return "TESS_EVALUATION";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
bool TryConvertToOpenGLShaderType(ShaderType type, GLenum& glShaderType) {
|
||||
switch (type) {
|
||||
case ShaderType::Vertex:
|
||||
glShaderType = GL_VERTEX_SHADER;
|
||||
return true;
|
||||
case ShaderType::Fragment:
|
||||
glShaderType = GL_FRAGMENT_SHADER;
|
||||
return true;
|
||||
case ShaderType::Geometry:
|
||||
glShaderType = GL_GEOMETRY_SHADER;
|
||||
return true;
|
||||
case ShaderType::Compute:
|
||||
glShaderType = GL_COMPUTE_SHADER;
|
||||
return true;
|
||||
case ShaderType::TessControl:
|
||||
case ShaderType::Hull:
|
||||
glShaderType = GL_TESS_CONTROL_SHADER;
|
||||
return true;
|
||||
case ShaderType::TessEvaluation:
|
||||
case ShaderType::Domain:
|
||||
glShaderType = GL_TESS_EVALUATION_SHADER;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CheckShaderCompileStatus(GLuint shader, const char* stageLabel, std::string* errorMessage) {
|
||||
GLint compileStatus = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
|
||||
if (compileStatus == GL_TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint infoLogLength = 0;
|
||||
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength);
|
||||
std::vector<char> infoLog(static_cast<size_t>(std::max(infoLogLength, 1)), '\0');
|
||||
glGetShaderInfoLog(shader, infoLogLength, nullptr, infoLog.data());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = std::string("ERROR::SHADER_COMPILATION_ERROR: ") + stageLabel + "\n" + infoLog.data();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CheckProgramLinkStatus(GLuint program, std::string* errorMessage) {
|
||||
GLint linkStatus = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
|
||||
if (linkStatus == GL_TRUE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
GLint infoLogLength = 0;
|
||||
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
|
||||
std::vector<char> infoLog(static_cast<size_t>(std::max(infoLogLength, 1)), '\0');
|
||||
glGetProgramInfoLog(program, infoLogLength, nullptr, infoLog.data());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = std::string("ERROR::PROGRAM_LINKING_ERROR\n") + infoLog.data();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompileOpenGLShaderHandleFromSource(const ShaderCompileDesc& desc,
|
||||
GLuint& outShaderHandle,
|
||||
ShaderType& outShaderType,
|
||||
std::string* errorMessage) {
|
||||
std::string sourceText;
|
||||
if (!LoadShaderSourceText(desc, sourceText) || sourceText.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to load GLSL shader source for OpenGL compilation.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveOpenGLShaderType(desc, sourceText, outShaderType)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to resolve OpenGL GLSL shader stage from profile, file extension, or source text.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GLenum glShaderType = 0;
|
||||
if (!TryConvertToOpenGLShaderType(outShaderType, glShaderType)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "OpenGL does not support the requested GLSL shader stage.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* sourcePtr = sourceText.c_str();
|
||||
const GLuint shaderHandle = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderHandle, 1, &sourcePtr, nullptr);
|
||||
glCompileShader(shaderHandle);
|
||||
if (!CheckShaderCompileStatus(shaderHandle, ShaderStageLabel(outShaderType), errorMessage)) {
|
||||
glDeleteShader(shaderHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
outShaderHandle = shaderHandle;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompileOpenGLShaderHandleFromSpirv(const ShaderCompileDesc& desc,
|
||||
GLuint& outShaderHandle,
|
||||
ShaderType& outShaderType,
|
||||
std::string* errorMessage) {
|
||||
CompiledSpirvShader compiledShader = {};
|
||||
if (!CompileSpirvShader(desc, SpirvTargetEnvironment::Vulkan, compiledShader, errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string glslSource;
|
||||
if (!TranspileSpirvToOpenGLGLSL(compiledShader, glslSource, errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLenum glShaderType = 0;
|
||||
if (!TryConvertToOpenGLShaderType(compiledShader.type, glShaderType)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "OpenGL does not support the requested shader stage after SPIR-V transpilation.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* sourcePtr = glslSource.c_str();
|
||||
const GLuint shaderHandle = glCreateShader(glShaderType);
|
||||
glShaderSource(shaderHandle, 1, &sourcePtr, nullptr);
|
||||
glCompileShader(shaderHandle);
|
||||
if (!CheckShaderCompileStatus(shaderHandle, ShaderStageLabel(compiledShader.type), errorMessage)) {
|
||||
glDeleteShader(shaderHandle);
|
||||
return false;
|
||||
}
|
||||
|
||||
outShaderHandle = shaderHandle;
|
||||
outShaderType = compiledShader.type;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompileOpenGLShaderHandle(const ShaderCompileDesc& desc,
|
||||
GLuint& outShaderHandle,
|
||||
ShaderType& outShaderType,
|
||||
std::string* errorMessage) {
|
||||
outShaderHandle = 0;
|
||||
outShaderType = ShaderType::Vertex;
|
||||
|
||||
if (desc.sourceLanguage == ShaderLanguage::GLSL) {
|
||||
return CompileOpenGLShaderHandleFromSource(desc, outShaderHandle, outShaderType, errorMessage);
|
||||
}
|
||||
|
||||
if (desc.sourceLanguage == ShaderLanguage::HLSL || desc.sourceLanguage == ShaderLanguage::SPIRV) {
|
||||
return CompileOpenGLShaderHandleFromSpirv(desc, outShaderHandle, outShaderType, errorMessage);
|
||||
}
|
||||
|
||||
if (!desc.fileName.empty()) {
|
||||
const std::wstring extension = std::filesystem::path(desc.fileName).extension().wstring();
|
||||
if (extension == L".hlsl" || extension == L".spv") {
|
||||
return CompileOpenGLShaderHandleFromSpirv(desc, outShaderHandle, outShaderType, errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
return CompileOpenGLShaderHandleFromSource(desc, outShaderHandle, outShaderType, errorMessage);
|
||||
}
|
||||
|
||||
bool BuildOpenGLProgram(const ShaderCompileDesc* shaderDescs,
|
||||
size_t shaderDescCount,
|
||||
GLuint& outProgram,
|
||||
ShaderType& outProgramType,
|
||||
std::string* errorMessage) {
|
||||
outProgram = 0;
|
||||
outProgramType = ShaderType::Vertex;
|
||||
if (shaderDescs == nullptr || shaderDescCount == 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "No OpenGL shader descriptors were provided.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint program = glCreateProgram();
|
||||
std::vector<GLuint> shaderHandles;
|
||||
shaderHandles.reserve(shaderDescCount);
|
||||
|
||||
for (size_t index = 0; index < shaderDescCount; ++index) {
|
||||
GLuint shaderHandle = 0;
|
||||
ShaderType shaderType = ShaderType::Vertex;
|
||||
if (!CompileOpenGLShaderHandle(shaderDescs[index], shaderHandle, shaderType, errorMessage)) {
|
||||
for (GLuint handle : shaderHandles) {
|
||||
glDetachShader(program, handle);
|
||||
glDeleteShader(handle);
|
||||
}
|
||||
glDeleteProgram(program);
|
||||
return false;
|
||||
}
|
||||
|
||||
glAttachShader(program, shaderHandle);
|
||||
shaderHandles.push_back(shaderHandle);
|
||||
if (index == 0) {
|
||||
outProgramType = shaderType;
|
||||
}
|
||||
}
|
||||
|
||||
glLinkProgram(program);
|
||||
const bool linked = CheckProgramLinkStatus(program, errorMessage);
|
||||
for (GLuint handle : shaderHandles) {
|
||||
glDetachShader(program, handle);
|
||||
glDeleteShader(handle);
|
||||
}
|
||||
|
||||
if (!linked) {
|
||||
glDeleteProgram(program);
|
||||
return false;
|
||||
}
|
||||
|
||||
outProgram = program;
|
||||
return true;
|
||||
}
|
||||
|
||||
OpenGLFormat ToOpenGLTextureFormat(Format format) {
|
||||
switch (format) {
|
||||
case Format::R8_UNorm:
|
||||
@@ -740,30 +1145,45 @@ RHIShader* OpenGLDevice::CreateShader(const ShaderCompileDesc& desc) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (desc.source.empty() && desc.fileName.empty()) {
|
||||
delete shader;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (desc.sourceLanguage == ShaderLanguage::GLSL && !desc.source.empty()) {
|
||||
const std::string entryPoint = NarrowAscii(desc.entryPoint);
|
||||
std::string profile = NarrowAscii(desc.profile);
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
const char* entryPointPtr = entryPoint.empty() ? nullptr : entryPoint.c_str();
|
||||
const char* profilePtr = profile.empty() ? nullptr : profile.c_str();
|
||||
|
||||
if (shader->Compile(desc.source.data(), desc.source.size(), entryPointPtr, profilePtr)) {
|
||||
return shader;
|
||||
}
|
||||
delete shader;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!desc.fileName.empty()) {
|
||||
std::wstring filePath = desc.fileName;
|
||||
std::string entryPoint = NarrowAscii(desc.entryPoint);
|
||||
std::string profile = NarrowAscii(desc.profile);
|
||||
if (shader->CompileFromFile(filePath.c_str(), entryPoint.c_str(), profile.c_str())) {
|
||||
} else if (!UsesSpirvCompilationPath(desc) &&
|
||||
(desc.sourceLanguage == ShaderLanguage::Unknown || desc.sourceLanguage == ShaderLanguage::GLSL) &&
|
||||
!desc.fileName.empty()) {
|
||||
const std::string entryPoint = NarrowAscii(desc.entryPoint);
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
if (shader->CompileFromFile(desc.fileName.c_str(), entryPoint.c_str(), profile.c_str())) {
|
||||
return shader;
|
||||
}
|
||||
delete shader;
|
||||
return nullptr;
|
||||
} else {
|
||||
GLuint program = 0;
|
||||
ShaderType shaderType = ShaderType::Vertex;
|
||||
std::string errorMessage;
|
||||
if (BuildOpenGLProgram(&desc, 1, program, shaderType, &errorMessage) &&
|
||||
shader->AdoptProgram(program, shaderType)) {
|
||||
return shader;
|
||||
}
|
||||
|
||||
if (!errorMessage.empty()) {
|
||||
XCEngine::Debug::Logger::Get().Error(XCEngine::Debug::LogCategory::Rendering, errorMessage.c_str());
|
||||
}
|
||||
if (program != 0) {
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
delete shader;
|
||||
return nullptr;
|
||||
}
|
||||
@@ -796,28 +1216,23 @@ RHIPipelineState* OpenGLDevice::CreatePipelineState(const GraphicsPipelineDesc&
|
||||
}
|
||||
|
||||
auto graphicsShader = std::make_unique<OpenGLShader>();
|
||||
bool compiled = false;
|
||||
if (!desc.vertexShader.source.empty() && !desc.fragmentShader.source.empty()) {
|
||||
const std::string vertexSource = SourceToString(desc.vertexShader);
|
||||
const std::string fragmentSource = SourceToString(desc.fragmentShader);
|
||||
if (hasGeometryShader && !desc.geometryShader.source.empty()) {
|
||||
const std::string geometrySource = SourceToString(desc.geometryShader);
|
||||
compiled = graphicsShader->Compile(vertexSource.c_str(), fragmentSource.c_str(), geometrySource.c_str());
|
||||
} else {
|
||||
compiled = graphicsShader->Compile(vertexSource.c_str(), fragmentSource.c_str());
|
||||
ShaderCompileDesc shaderDescs[3] = {
|
||||
desc.vertexShader,
|
||||
desc.fragmentShader,
|
||||
desc.geometryShader
|
||||
};
|
||||
const size_t shaderDescCount = hasGeometryShader ? 3u : 2u;
|
||||
GLuint program = 0;
|
||||
ShaderType programType = ShaderType::Vertex;
|
||||
std::string errorMessage;
|
||||
if (!BuildOpenGLProgram(shaderDescs, shaderDescCount, program, programType, &errorMessage) ||
|
||||
!graphicsShader->AdoptProgram(program, programType)) {
|
||||
if (!errorMessage.empty()) {
|
||||
XCEngine::Debug::Logger::Get().Error(XCEngine::Debug::LogCategory::Rendering, errorMessage.c_str());
|
||||
}
|
||||
} else if (!desc.vertexShader.fileName.empty() && !desc.fragmentShader.fileName.empty()) {
|
||||
const std::string vertexPath = NarrowAscii(desc.vertexShader.fileName);
|
||||
const std::string fragmentPath = NarrowAscii(desc.fragmentShader.fileName);
|
||||
if (hasGeometryShader && !desc.geometryShader.fileName.empty()) {
|
||||
const std::string geometryPath = NarrowAscii(desc.geometryShader.fileName);
|
||||
compiled = graphicsShader->CompileFromFile(vertexPath.c_str(), fragmentPath.c_str(), geometryPath.c_str());
|
||||
} else {
|
||||
compiled = graphicsShader->CompileFromFile(vertexPath.c_str(), fragmentPath.c_str());
|
||||
if (program != 0) {
|
||||
glDeleteProgram(program);
|
||||
}
|
||||
}
|
||||
|
||||
if (!compiled) {
|
||||
delete pso;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -374,6 +374,22 @@ bool OpenGLShader::Compile(const void* sourceData, size_t sourceSize, const char
|
||||
return Compile(source.c_str(), type);
|
||||
}
|
||||
|
||||
bool OpenGLShader::AdoptProgram(unsigned int program, ShaderType type) {
|
||||
if (program == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_program != 0 && m_program != program) {
|
||||
glDeleteProgram(m_program);
|
||||
}
|
||||
|
||||
m_program = program;
|
||||
m_type = type;
|
||||
m_uniformInfos.clear();
|
||||
m_uniformsCached = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void OpenGLShader::Shutdown() {
|
||||
if (m_program) {
|
||||
glDeleteProgram(m_program);
|
||||
|
||||
936
engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp
Normal file
936
engine/src/RHI/ShaderCompiler/SpirvShaderCompiler.cpp
Normal file
@@ -0,0 +1,936 @@
|
||||
#include "XCEngine/RHI/ShaderCompiler/SpirvShaderCompiler.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string NarrowAscii(const std::wstring& value) {
|
||||
std::string result;
|
||||
result.reserve(value.size());
|
||||
for (wchar_t ch : value) {
|
||||
result.push_back(static_cast<char>(ch));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring WidenAscii(const char* value) {
|
||||
if (value == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const std::string ascii(value);
|
||||
return std::wstring(ascii.begin(), ascii.end());
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromTarget(const char* target, ShaderType& type) {
|
||||
if (target == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (std::strstr(target, "vs_") != nullptr || std::strcmp(target, "vs") == 0) {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(target, "ps_") != nullptr ||
|
||||
std::strstr(target, "fs_") != nullptr ||
|
||||
std::strcmp(target, "ps") == 0 ||
|
||||
std::strcmp(target, "fs") == 0) {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(target, "gs_") != nullptr || std::strcmp(target, "gs") == 0) {
|
||||
type = ShaderType::Geometry;
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(target, "hs_") != nullptr || std::strcmp(target, "hs") == 0) {
|
||||
type = ShaderType::TessControl;
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(target, "ds_") != nullptr || std::strcmp(target, "ds") == 0) {
|
||||
type = ShaderType::TessEvaluation;
|
||||
return true;
|
||||
}
|
||||
if (std::strstr(target, "cs_") != nullptr || std::strcmp(target, "cs") == 0) {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr uint32_t kSpirvMagic = 0x07230203u;
|
||||
constexpr uint16_t kSpirvOpEntryPoint = 15;
|
||||
|
||||
ShaderType ToShaderType(uint32_t executionModel) {
|
||||
switch (executionModel) {
|
||||
case 0:
|
||||
return ShaderType::Vertex;
|
||||
case 1:
|
||||
return ShaderType::TessControl;
|
||||
case 2:
|
||||
return ShaderType::TessEvaluation;
|
||||
case 3:
|
||||
return ShaderType::Geometry;
|
||||
case 4:
|
||||
return ShaderType::Fragment;
|
||||
case 5:
|
||||
return ShaderType::Compute;
|
||||
default:
|
||||
return ShaderType::Vertex;
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadBinaryFile(const std::filesystem::path& path, std::vector<uint8_t>& bytes) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes.resize(static_cast<size_t>(size));
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (!file.read(reinterpret_cast<char*>(bytes.data()), size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadTextFile(const std::filesystem::path& path, std::string& text) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
text.resize(static_cast<size_t>(size));
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (!file.read(text.data(), size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& text) {
|
||||
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(text.data(), static_cast<std::streamsize>(text.size()));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
bool ParseSpirvMetadata(const uint32_t* words,
|
||||
size_t wordCount,
|
||||
ShaderType& type,
|
||||
std::string& entryPoint) {
|
||||
if (words == nullptr || wordCount < 5 || words[0] != kSpirvMagic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t index = 5;
|
||||
while (index < wordCount) {
|
||||
const uint32_t instruction = words[index];
|
||||
const uint16_t wordCountInInstruction = static_cast<uint16_t>(instruction >> 16);
|
||||
const uint16_t opcode = static_cast<uint16_t>(instruction & 0xFFFFu);
|
||||
if (wordCountInInstruction == 0 || index + wordCountInInstruction > wordCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opcode == kSpirvOpEntryPoint && wordCountInInstruction >= 4) {
|
||||
type = ToShaderType(words[index + 1]);
|
||||
entryPoint = reinterpret_cast<const char*>(&words[index + 3]);
|
||||
return true;
|
||||
}
|
||||
|
||||
index += wordCountInInstruction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromPath(const std::filesystem::path& path, ShaderType& type) {
|
||||
std::string extension = path.extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
|
||||
if (extension == ".vert" || extension == ".vs") {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".frag" || extension == ".ps" || extension == ".fs") {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".geom" || extension == ".gs") {
|
||||
type = ShaderType::Geometry;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".tesc" || extension == ".hs") {
|
||||
type = ShaderType::TessControl;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".tese" || extension == ".ds") {
|
||||
type = ShaderType::TessEvaluation;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".comp" || extension == ".cs") {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromSource(std::string_view source, ShaderType& type) {
|
||||
std::string lowered(source.begin(), source.end());
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
|
||||
if (lowered.find("local_size_x") != std::string::npos ||
|
||||
lowered.find("gl_globalinvocationid") != std::string::npos ||
|
||||
lowered.find("imagestore") != std::string::npos ||
|
||||
lowered.find("imageload") != std::string::npos) {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_position") != std::string::npos) {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_fragcoord") != std::string::npos ||
|
||||
lowered.find("gl_fragdepth") != std::string::npos ||
|
||||
lowered.find("fragcolor") != std::string::npos) {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveShaderType(const ShaderCompileDesc& desc,
|
||||
std::string_view sourceText,
|
||||
ShaderType& type) {
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
if (TryResolveShaderTypeFromTarget(profile.c_str(), type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!desc.fileName.empty() && TryResolveShaderTypeFromPath(std::filesystem::path(desc.fileName), type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryResolveShaderTypeFromSource(sourceText, type);
|
||||
}
|
||||
|
||||
bool IsSpirvInput(const ShaderCompileDesc& desc) {
|
||||
return desc.sourceLanguage == ShaderLanguage::SPIRV ||
|
||||
(!desc.fileName.empty() && std::filesystem::path(desc.fileName).extension() == L".spv");
|
||||
}
|
||||
|
||||
bool IsHlslInput(const ShaderCompileDesc& desc) {
|
||||
if (desc.sourceLanguage == ShaderLanguage::HLSL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (desc.fileName.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string extension = std::filesystem::path(desc.fileName).extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return extension == ".hlsl";
|
||||
}
|
||||
|
||||
std::wstring GetEnvironmentVariableValue(const wchar_t* name) {
|
||||
const DWORD length = GetEnvironmentVariableW(name, nullptr, 0);
|
||||
if (length == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring value(static_cast<size_t>(length), L'\0');
|
||||
const DWORD written = GetEnvironmentVariableW(name, value.data(), length);
|
||||
if (written == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
value.resize(written);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool FindGlslangValidator(std::wstring& outPath) {
|
||||
const std::wstring vulkanSdk = GetEnvironmentVariableValue(L"VULKAN_SDK");
|
||||
if (!vulkanSdk.empty()) {
|
||||
const std::filesystem::path sdkCandidate = std::filesystem::path(vulkanSdk) / L"Bin" / L"glslangValidator.exe";
|
||||
if (std::filesystem::exists(sdkCandidate)) {
|
||||
outPath = sdkCandidate.wstring();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> candidates;
|
||||
const std::filesystem::path sdkRoot = L"D:/VulkanSDK";
|
||||
if (std::filesystem::exists(sdkRoot) && std::filesystem::is_directory(sdkRoot)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sdkRoot)) {
|
||||
if (!entry.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path candidate = entry.path() / L"Bin" / L"glslangValidator.exe";
|
||||
if (std::filesystem::exists(candidate)) {
|
||||
candidates.push_back(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates.empty()) {
|
||||
std::sort(candidates.begin(), candidates.end(), [](const std::filesystem::path& lhs, const std::filesystem::path& rhs) {
|
||||
return lhs.wstring() > rhs.wstring();
|
||||
});
|
||||
outPath = candidates.front().wstring();
|
||||
return true;
|
||||
}
|
||||
|
||||
wchar_t buffer[MAX_PATH] = {};
|
||||
const DWORD length = SearchPathW(nullptr, L"glslangValidator.exe", nullptr, MAX_PATH, buffer, nullptr);
|
||||
if (length > 0 && length < MAX_PATH) {
|
||||
outPath = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool FindSpirvCross(std::wstring& outPath) {
|
||||
const std::wstring vulkanSdk = GetEnvironmentVariableValue(L"VULKAN_SDK");
|
||||
if (!vulkanSdk.empty()) {
|
||||
const std::filesystem::path sdkCandidate = std::filesystem::path(vulkanSdk) / L"Bin" / L"spirv-cross.exe";
|
||||
if (std::filesystem::exists(sdkCandidate)) {
|
||||
outPath = sdkCandidate.wstring();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> candidates;
|
||||
const std::filesystem::path sdkRoot = L"D:/VulkanSDK";
|
||||
if (std::filesystem::exists(sdkRoot) && std::filesystem::is_directory(sdkRoot)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sdkRoot)) {
|
||||
if (!entry.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path candidate = entry.path() / L"Bin" / L"spirv-cross.exe";
|
||||
if (std::filesystem::exists(candidate)) {
|
||||
candidates.push_back(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates.empty()) {
|
||||
std::sort(candidates.begin(), candidates.end(), [](const std::filesystem::path& lhs, const std::filesystem::path& rhs) {
|
||||
return lhs.wstring() > rhs.wstring();
|
||||
});
|
||||
outPath = candidates.front().wstring();
|
||||
return true;
|
||||
}
|
||||
|
||||
wchar_t buffer[MAX_PATH] = {};
|
||||
const DWORD length = SearchPathW(nullptr, L"spirv-cross.exe", nullptr, MAX_PATH, buffer, nullptr);
|
||||
if (length > 0 && length < MAX_PATH) {
|
||||
outPath = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring ShaderStageArgument(ShaderType type) {
|
||||
switch (type) {
|
||||
case ShaderType::Vertex:
|
||||
return L"vert";
|
||||
case ShaderType::Fragment:
|
||||
return L"frag";
|
||||
case ShaderType::Geometry:
|
||||
return L"geom";
|
||||
case ShaderType::TessControl:
|
||||
return L"tesc";
|
||||
case ShaderType::TessEvaluation:
|
||||
return L"tese";
|
||||
case ShaderType::Compute:
|
||||
return L"comp";
|
||||
default:
|
||||
return L"vert";
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring ShaderStageExtension(ShaderType type) {
|
||||
return std::wstring(L".") + ShaderStageArgument(type);
|
||||
}
|
||||
|
||||
std::wstring ShaderSourceExtension(ShaderType type, bool isHlsl) {
|
||||
if (isHlsl) {
|
||||
switch (type) {
|
||||
case ShaderType::Vertex:
|
||||
return L".vert.hlsl";
|
||||
case ShaderType::Fragment:
|
||||
return L".frag.hlsl";
|
||||
case ShaderType::Geometry:
|
||||
return L".geom.hlsl";
|
||||
case ShaderType::TessControl:
|
||||
return L".tesc.hlsl";
|
||||
case ShaderType::TessEvaluation:
|
||||
return L".tese.hlsl";
|
||||
case ShaderType::Compute:
|
||||
return L".comp.hlsl";
|
||||
default:
|
||||
return L".shader.hlsl";
|
||||
}
|
||||
}
|
||||
|
||||
return ShaderStageExtension(type);
|
||||
}
|
||||
|
||||
std::wstring SpirvTargetArguments(SpirvTargetEnvironment targetEnvironment) {
|
||||
switch (targetEnvironment) {
|
||||
case SpirvTargetEnvironment::OpenGL:
|
||||
return L"-G";
|
||||
case SpirvTargetEnvironment::Vulkan:
|
||||
default:
|
||||
return L"-V --target-env vulkan1.0";
|
||||
}
|
||||
}
|
||||
|
||||
const char* SpirvTargetName(SpirvTargetEnvironment targetEnvironment) {
|
||||
switch (targetEnvironment) {
|
||||
case SpirvTargetEnvironment::OpenGL:
|
||||
return "OpenGL";
|
||||
case SpirvTargetEnvironment::Vulkan:
|
||||
default:
|
||||
return "Vulkan";
|
||||
}
|
||||
}
|
||||
|
||||
bool CreateTemporaryPath(const wchar_t* extension, std::wstring& outPath) {
|
||||
wchar_t tempDirectory[MAX_PATH] = {};
|
||||
if (GetTempPathW(MAX_PATH, tempDirectory) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t tempFile[MAX_PATH] = {};
|
||||
if (GetTempFileNameW(tempDirectory, L"XCV", 0, tempFile) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeleteFileW(tempFile);
|
||||
outPath = tempFile;
|
||||
outPath += extension;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteBinaryFile(const std::filesystem::path& path, const void* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<const char*>(data), static_cast<std::streamsize>(size));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
std::string InjectMacrosIntoSource(const std::string& source, const std::vector<ShaderCompileMacro>& macros) {
|
||||
if (macros.empty()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string macroBlock;
|
||||
for (const ShaderCompileMacro& macro : macros) {
|
||||
macroBlock += "#define ";
|
||||
macroBlock += NarrowAscii(macro.name);
|
||||
const std::string definition = NarrowAscii(macro.definition);
|
||||
if (!definition.empty()) {
|
||||
macroBlock += ' ';
|
||||
macroBlock += definition;
|
||||
}
|
||||
macroBlock += '\n';
|
||||
}
|
||||
|
||||
if (source.rfind("#version", 0) == 0) {
|
||||
const size_t lineEnd = source.find('\n');
|
||||
if (lineEnd != std::string::npos) {
|
||||
std::string result;
|
||||
result.reserve(source.size() + macroBlock.size());
|
||||
result.append(source, 0, lineEnd + 1);
|
||||
result += macroBlock;
|
||||
result.append(source, lineEnd + 1, std::string::npos);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return macroBlock + source;
|
||||
}
|
||||
|
||||
bool RunProcessAndCapture(const std::wstring& executablePath,
|
||||
const std::wstring& arguments,
|
||||
DWORD& exitCode,
|
||||
std::string& output) {
|
||||
SECURITY_ATTRIBUTES securityAttributes = {};
|
||||
securityAttributes.nLength = sizeof(securityAttributes);
|
||||
securityAttributes.bInheritHandle = TRUE;
|
||||
|
||||
HANDLE readPipe = nullptr;
|
||||
HANDLE writePipe = nullptr;
|
||||
if (!CreatePipe(&readPipe, &writePipe, &securityAttributes, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
STARTUPINFOW startupInfo = {};
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
startupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
startupInfo.hStdOutput = writePipe;
|
||||
startupInfo.hStdError = writePipe;
|
||||
|
||||
PROCESS_INFORMATION processInfo = {};
|
||||
std::wstring commandLine = L"\"" + executablePath + L"\" " + arguments;
|
||||
std::vector<wchar_t> commandLineBuffer(commandLine.begin(), commandLine.end());
|
||||
commandLineBuffer.push_back(L'\0');
|
||||
|
||||
const BOOL processCreated = CreateProcessW(nullptr,
|
||||
commandLineBuffer.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
TRUE,
|
||||
CREATE_NO_WINDOW,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&startupInfo,
|
||||
&processInfo);
|
||||
|
||||
CloseHandle(writePipe);
|
||||
writePipe = nullptr;
|
||||
|
||||
if (!processCreated) {
|
||||
CloseHandle(readPipe);
|
||||
return false;
|
||||
}
|
||||
|
||||
char buffer[4096] = {};
|
||||
DWORD bytesRead = 0;
|
||||
while (ReadFile(readPipe, buffer, sizeof(buffer), &bytesRead, nullptr) && bytesRead > 0) {
|
||||
output.append(buffer, bytesRead);
|
||||
}
|
||||
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
GetExitCodeProcess(processInfo.hProcess, &exitCode);
|
||||
|
||||
CloseHandle(processInfo.hThread);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(readPipe);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompileGlslToSpirv(const ShaderCompileDesc& desc,
|
||||
const std::string& sourceText,
|
||||
SpirvTargetEnvironment targetEnvironment,
|
||||
ShaderType type,
|
||||
const std::string& entryPoint,
|
||||
std::vector<uint32_t>& spirvWords,
|
||||
std::string* errorMessage) {
|
||||
std::wstring validatorPath;
|
||||
if (!FindGlslangValidator(validatorPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "glslangValidator.exe was not found. Install Vulkan SDK or add it to PATH.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring tempSourcePath;
|
||||
std::wstring tempOutputPath;
|
||||
if (!CreateTemporaryPath(ShaderSourceExtension(type, false).c_str(), tempSourcePath) ||
|
||||
!CreateTemporaryPath(L".spv", tempOutputPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to allocate temporary shader compiler files.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourceWithMacros = InjectMacrosIntoSource(sourceText, desc.macros);
|
||||
const bool wroteSource = WriteTextFile(tempSourcePath, sourceWithMacros);
|
||||
if (!wroteSource) {
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to write temporary GLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring arguments =
|
||||
SpirvTargetArguments(targetEnvironment) + L" -S " + ShaderStageArgument(type) +
|
||||
L" -e " + WidenAscii(entryPoint.c_str()) +
|
||||
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
|
||||
if (!ranProcess) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to launch glslangValidator.exe.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadedOutput) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = compilerOutput.empty()
|
||||
? std::string("glslangValidator.exe failed to compile GLSL to SPIR-V for ") +
|
||||
SpirvTargetName(targetEnvironment) + "."
|
||||
: compilerOutput;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Compiled SPIR-V payload size is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
spirvWords.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(spirvWords.data(), bytes.data(), bytes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
|
||||
const std::string& sourceText,
|
||||
SpirvTargetEnvironment targetEnvironment,
|
||||
ShaderType type,
|
||||
const std::string& entryPoint,
|
||||
std::vector<uint32_t>& spirvWords,
|
||||
std::string* errorMessage) {
|
||||
std::wstring validatorPath;
|
||||
if (!FindGlslangValidator(validatorPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "glslangValidator.exe was not found. Install Vulkan SDK or add it to PATH.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring tempSourcePath;
|
||||
std::wstring tempOutputPath;
|
||||
if (!CreateTemporaryPath(ShaderSourceExtension(type, true).c_str(), tempSourcePath) ||
|
||||
!CreateTemporaryPath(L".spv", tempOutputPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to allocate temporary shader compiler files.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourceWithMacros = InjectMacrosIntoSource(sourceText, desc.macros);
|
||||
const bool wroteSource = WriteTextFile(tempSourcePath, sourceWithMacros);
|
||||
if (!wroteSource) {
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to write temporary HLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring arguments =
|
||||
L"-D --auto-map-bindings " + SpirvTargetArguments(targetEnvironment) +
|
||||
L" -S " + ShaderStageArgument(type) +
|
||||
L" -e " + WidenAscii(entryPoint.c_str()) +
|
||||
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
|
||||
if (!ranProcess) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to launch glslangValidator.exe.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadedOutput) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = compilerOutput.empty()
|
||||
? std::string("glslangValidator.exe failed to compile HLSL to SPIR-V for ") +
|
||||
SpirvTargetName(targetEnvironment) + "."
|
||||
: compilerOutput;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Compiled SPIR-V payload size is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
spirvWords.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(spirvWords.data(), bytes.data(), bytes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CompileSpirvShader(const ShaderCompileDesc& desc,
|
||||
SpirvTargetEnvironment targetEnvironment,
|
||||
CompiledSpirvShader& outShader,
|
||||
std::string* errorMessage) {
|
||||
outShader = {};
|
||||
outShader.entryPoint = NarrowAscii(desc.entryPoint);
|
||||
if (outShader.entryPoint.empty()) {
|
||||
outShader.entryPoint = "main";
|
||||
}
|
||||
|
||||
if (desc.source.empty() && desc.fileName.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "No shader source or file name was provided.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsSpirvInput(desc)) {
|
||||
std::vector<uint8_t> bytes;
|
||||
if (!desc.source.empty()) {
|
||||
bytes = desc.source;
|
||||
} else if (!LoadBinaryFile(std::filesystem::path(desc.fileName), bytes)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to read SPIR-V shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "SPIR-V payload size is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
outShader.spirvWords.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(outShader.spirvWords.data(), bytes.data(), bytes.size());
|
||||
|
||||
if (outShader.spirvWords.empty() || outShader.spirvWords[0] != kSpirvMagic) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "SPIR-V payload does not contain a valid SPIR-V header.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string parsedEntryPoint;
|
||||
if (!ParseSpirvMetadata(outShader.spirvWords.data(),
|
||||
outShader.spirvWords.size(),
|
||||
outShader.type,
|
||||
parsedEntryPoint)) {
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
if (!TryResolveShaderTypeFromTarget(profile.c_str(), outShader.type)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to parse SPIR-V shader metadata.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else if (desc.entryPoint.empty() && !parsedEntryPoint.empty()) {
|
||||
outShader.entryPoint = parsedEntryPoint;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string sourceText;
|
||||
if (!desc.source.empty()) {
|
||||
sourceText.assign(reinterpret_cast<const char*>(desc.source.data()), desc.source.size());
|
||||
} else if (!LoadTextFile(std::filesystem::path(desc.fileName), sourceText)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = IsHlslInput(desc)
|
||||
? "Failed to read HLSL shader file."
|
||||
: "Failed to read GLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceText.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = IsHlslInput(desc)
|
||||
? "HLSL shader source is empty."
|
||||
: "GLSL shader source is empty.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveShaderType(desc, sourceText, outShader.type)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = std::string("Failed to resolve ") + SpirvTargetName(targetEnvironment) +
|
||||
" shader stage from profile, file extension, or source text.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsHlslInput(desc)) {
|
||||
if (!CompileHlslToSpirv(desc,
|
||||
sourceText,
|
||||
targetEnvironment,
|
||||
outShader.type,
|
||||
outShader.entryPoint,
|
||||
outShader.spirvWords,
|
||||
errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!CompileGlslToSpirv(desc,
|
||||
sourceText,
|
||||
targetEnvironment,
|
||||
outShader.type,
|
||||
outShader.entryPoint,
|
||||
outShader.spirvWords,
|
||||
errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderType parsedType = outShader.type;
|
||||
std::string parsedEntryPoint;
|
||||
if (ParseSpirvMetadata(outShader.spirvWords.data(),
|
||||
outShader.spirvWords.size(),
|
||||
parsedType,
|
||||
parsedEntryPoint)) {
|
||||
outShader.type = parsedType;
|
||||
if (desc.entryPoint.empty() && !parsedEntryPoint.empty()) {
|
||||
outShader.entryPoint = parsedEntryPoint;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TranspileSpirvToOpenGLGLSL(const CompiledSpirvShader& shader,
|
||||
std::string& outSourceText,
|
||||
std::string* errorMessage) {
|
||||
outSourceText.clear();
|
||||
if (shader.spirvWords.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "No SPIR-V payload was provided for GLSL transpilation.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring spirvCrossPath;
|
||||
if (!FindSpirvCross(spirvCrossPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "spirv-cross.exe was not found. Install Vulkan SDK or add it to PATH.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring tempSpirvPath;
|
||||
std::wstring tempGlslPath;
|
||||
if (!CreateTemporaryPath(L".spv", tempSpirvPath) ||
|
||||
!CreateTemporaryPath(L".glsl", tempGlslPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to allocate temporary files for SPIR-V GLSL transpilation.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool wroteSpirv = WriteBinaryFile(
|
||||
std::filesystem::path(tempSpirvPath),
|
||||
shader.spirvWords.data(),
|
||||
shader.spirvWords.size() * sizeof(uint32_t));
|
||||
if (!wroteSpirv) {
|
||||
DeleteFileW(tempSpirvPath.c_str());
|
||||
DeleteFileW(tempGlslPath.c_str());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to write temporary SPIR-V input for spirv-cross.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring arguments =
|
||||
L"--version 430 --output \"" + tempGlslPath + L"\" \"" + tempSpirvPath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string toolOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(spirvCrossPath, arguments, exitCode, toolOutput);
|
||||
|
||||
std::string glslSource;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 &&
|
||||
LoadTextFile(std::filesystem::path(tempGlslPath), glslSource);
|
||||
|
||||
DeleteFileW(tempSpirvPath.c_str());
|
||||
DeleteFileW(tempGlslPath.c_str());
|
||||
|
||||
if (!ranProcess) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to launch spirv-cross.exe.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadedOutput || glslSource.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = toolOutput.empty()
|
||||
? "spirv-cross.exe failed to transpile SPIR-V to OpenGL GLSL."
|
||||
: toolOutput;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
outSourceText = std::move(glslSource);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
@@ -1,721 +1,12 @@
|
||||
#include "XCEngine/RHI/Vulkan/VulkanShaderCompiler.h"
|
||||
|
||||
#include "XCEngine/RHI/Vulkan/VulkanCommon.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kSpirvMagic = 0x07230203u;
|
||||
constexpr uint16_t kSpirvOpEntryPoint = 15;
|
||||
|
||||
ShaderType ToShaderType(uint32_t executionModel) {
|
||||
switch (executionModel) {
|
||||
case 0:
|
||||
return ShaderType::Vertex;
|
||||
case 1:
|
||||
return ShaderType::TessControl;
|
||||
case 2:
|
||||
return ShaderType::TessEvaluation;
|
||||
case 3:
|
||||
return ShaderType::Geometry;
|
||||
case 4:
|
||||
return ShaderType::Fragment;
|
||||
case 5:
|
||||
return ShaderType::Compute;
|
||||
default:
|
||||
return ShaderType::Vertex;
|
||||
}
|
||||
}
|
||||
|
||||
bool LoadBinaryFile(const std::filesystem::path& path, std::vector<uint8_t>& bytes) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes.resize(static_cast<size_t>(size));
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (!file.read(reinterpret_cast<char*>(bytes.data()), size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LoadTextFile(const std::filesystem::path& path, std::string& text) {
|
||||
std::ifstream file(path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
text.resize(static_cast<size_t>(size));
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (!file.read(text.data(), size)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteTextFile(const std::filesystem::path& path, const std::string& text) {
|
||||
std::ofstream file(path, std::ios::binary | std::ios::trunc);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(text.data(), static_cast<std::streamsize>(text.size()));
|
||||
return file.good();
|
||||
}
|
||||
|
||||
bool ParseSpirvMetadata(const uint32_t* words,
|
||||
size_t wordCount,
|
||||
ShaderType& type,
|
||||
std::string& entryPoint) {
|
||||
if (words == nullptr || wordCount < 5 || words[0] != kSpirvMagic) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t index = 5;
|
||||
while (index < wordCount) {
|
||||
const uint32_t instruction = words[index];
|
||||
const uint16_t wordCountInInstruction = static_cast<uint16_t>(instruction >> 16);
|
||||
const uint16_t opcode = static_cast<uint16_t>(instruction & 0xFFFFu);
|
||||
if (wordCountInInstruction == 0 || index + wordCountInInstruction > wordCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opcode == kSpirvOpEntryPoint && wordCountInInstruction >= 4) {
|
||||
type = ToShaderType(words[index + 1]);
|
||||
entryPoint = reinterpret_cast<const char*>(&words[index + 3]);
|
||||
return true;
|
||||
}
|
||||
|
||||
index += wordCountInInstruction;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromPath(const std::filesystem::path& path, ShaderType& type) {
|
||||
std::string extension = path.extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
|
||||
if (extension == ".vert" || extension == ".vs") {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".frag" || extension == ".ps" || extension == ".fs") {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".geom" || extension == ".gs") {
|
||||
type = ShaderType::Geometry;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".tesc" || extension == ".hs") {
|
||||
type = ShaderType::TessControl;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".tese" || extension == ".ds") {
|
||||
type = ShaderType::TessEvaluation;
|
||||
return true;
|
||||
}
|
||||
if (extension == ".comp" || extension == ".cs") {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryResolveShaderTypeFromSource(std::string_view source, ShaderType& type) {
|
||||
std::string lowered(source.begin(), source.end());
|
||||
std::transform(lowered.begin(), lowered.end(), lowered.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
|
||||
if (lowered.find("local_size_x") != std::string::npos ||
|
||||
lowered.find("gl_globalinvocationid") != std::string::npos ||
|
||||
lowered.find("imagestore") != std::string::npos ||
|
||||
lowered.find("imageload") != std::string::npos) {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_position") != std::string::npos) {
|
||||
type = ShaderType::Vertex;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (lowered.find("gl_fragcoord") != std::string::npos ||
|
||||
lowered.find("gl_fragdepth") != std::string::npos ||
|
||||
lowered.find("fragcolor") != std::string::npos) {
|
||||
type = ShaderType::Fragment;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ResolveShaderType(const ShaderCompileDesc& desc,
|
||||
std::string_view sourceText,
|
||||
ShaderType& type) {
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
if (TryResolveShaderTypeFromTarget(profile.c_str(), type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!desc.fileName.empty() && TryResolveShaderTypeFromPath(std::filesystem::path(desc.fileName), type)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryResolveShaderTypeFromSource(sourceText, type);
|
||||
}
|
||||
|
||||
bool IsSpirvInput(const ShaderCompileDesc& desc) {
|
||||
return desc.sourceLanguage == ShaderLanguage::SPIRV ||
|
||||
(!desc.fileName.empty() && std::filesystem::path(desc.fileName).extension() == L".spv");
|
||||
}
|
||||
|
||||
bool IsHlslInput(const ShaderCompileDesc& desc) {
|
||||
if (desc.sourceLanguage == ShaderLanguage::HLSL) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (desc.fileName.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string extension = std::filesystem::path(desc.fileName).extension().string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char ch) {
|
||||
return static_cast<char>(std::tolower(ch));
|
||||
});
|
||||
return extension == ".hlsl";
|
||||
}
|
||||
|
||||
std::wstring GetEnvironmentVariableValue(const wchar_t* name) {
|
||||
const DWORD length = GetEnvironmentVariableW(name, nullptr, 0);
|
||||
if (length == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::wstring value(static_cast<size_t>(length), L'\0');
|
||||
const DWORD written = GetEnvironmentVariableW(name, value.data(), length);
|
||||
if (written == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
value.resize(written);
|
||||
return value;
|
||||
}
|
||||
|
||||
bool FindGlslangValidator(std::wstring& outPath) {
|
||||
const std::wstring vulkanSdk = GetEnvironmentVariableValue(L"VULKAN_SDK");
|
||||
if (!vulkanSdk.empty()) {
|
||||
const std::filesystem::path sdkCandidate = std::filesystem::path(vulkanSdk) / L"Bin" / L"glslangValidator.exe";
|
||||
if (std::filesystem::exists(sdkCandidate)) {
|
||||
outPath = sdkCandidate.wstring();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> candidates;
|
||||
const std::filesystem::path sdkRoot = L"D:/VulkanSDK";
|
||||
if (std::filesystem::exists(sdkRoot) && std::filesystem::is_directory(sdkRoot)) {
|
||||
for (const auto& entry : std::filesystem::directory_iterator(sdkRoot)) {
|
||||
if (!entry.is_directory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path candidate = entry.path() / L"Bin" / L"glslangValidator.exe";
|
||||
if (std::filesystem::exists(candidate)) {
|
||||
candidates.push_back(candidate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates.empty()) {
|
||||
std::sort(candidates.begin(), candidates.end(), [](const std::filesystem::path& lhs, const std::filesystem::path& rhs) {
|
||||
return lhs.wstring() > rhs.wstring();
|
||||
});
|
||||
outPath = candidates.front().wstring();
|
||||
return true;
|
||||
}
|
||||
|
||||
wchar_t buffer[MAX_PATH] = {};
|
||||
const DWORD length = SearchPathW(nullptr, L"glslangValidator.exe", nullptr, MAX_PATH, buffer, nullptr);
|
||||
if (length > 0 && length < MAX_PATH) {
|
||||
outPath = buffer;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring ShaderStageArgument(ShaderType type) {
|
||||
switch (type) {
|
||||
case ShaderType::Vertex:
|
||||
return L"vert";
|
||||
case ShaderType::Fragment:
|
||||
return L"frag";
|
||||
case ShaderType::Geometry:
|
||||
return L"geom";
|
||||
case ShaderType::TessControl:
|
||||
return L"tesc";
|
||||
case ShaderType::TessEvaluation:
|
||||
return L"tese";
|
||||
case ShaderType::Compute:
|
||||
return L"comp";
|
||||
default:
|
||||
return L"vert";
|
||||
}
|
||||
}
|
||||
|
||||
std::wstring ShaderStageExtension(ShaderType type) {
|
||||
return std::wstring(L".") + ShaderStageArgument(type);
|
||||
}
|
||||
|
||||
std::wstring ShaderSourceExtension(ShaderType type, bool isHlsl) {
|
||||
if (isHlsl) {
|
||||
switch (type) {
|
||||
case ShaderType::Vertex:
|
||||
return L".vert.hlsl";
|
||||
case ShaderType::Fragment:
|
||||
return L".frag.hlsl";
|
||||
case ShaderType::Geometry:
|
||||
return L".geom.hlsl";
|
||||
case ShaderType::TessControl:
|
||||
return L".tesc.hlsl";
|
||||
case ShaderType::TessEvaluation:
|
||||
return L".tese.hlsl";
|
||||
case ShaderType::Compute:
|
||||
return L".comp.hlsl";
|
||||
default:
|
||||
return L".shader.hlsl";
|
||||
}
|
||||
}
|
||||
|
||||
return ShaderStageExtension(type);
|
||||
}
|
||||
|
||||
bool CreateTemporaryPath(const wchar_t* extension, std::wstring& outPath) {
|
||||
wchar_t tempDirectory[MAX_PATH] = {};
|
||||
if (GetTempPathW(MAX_PATH, tempDirectory) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t tempFile[MAX_PATH] = {};
|
||||
if (GetTempFileNameW(tempDirectory, L"XCV", 0, tempFile) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
DeleteFileW(tempFile);
|
||||
outPath = tempFile;
|
||||
outPath += extension;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string InjectMacrosIntoSource(const std::string& source, const std::vector<ShaderCompileMacro>& macros) {
|
||||
if (macros.empty()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string macroBlock;
|
||||
for (const ShaderCompileMacro& macro : macros) {
|
||||
macroBlock += "#define ";
|
||||
macroBlock += NarrowAscii(macro.name);
|
||||
const std::string definition = NarrowAscii(macro.definition);
|
||||
if (!definition.empty()) {
|
||||
macroBlock += ' ';
|
||||
macroBlock += definition;
|
||||
}
|
||||
macroBlock += '\n';
|
||||
}
|
||||
|
||||
if (source.rfind("#version", 0) == 0) {
|
||||
const size_t lineEnd = source.find('\n');
|
||||
if (lineEnd != std::string::npos) {
|
||||
std::string result;
|
||||
result.reserve(source.size() + macroBlock.size());
|
||||
result.append(source, 0, lineEnd + 1);
|
||||
result += macroBlock;
|
||||
result.append(source, lineEnd + 1, std::string::npos);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return macroBlock + source;
|
||||
}
|
||||
|
||||
bool RunProcessAndCapture(const std::wstring& executablePath,
|
||||
const std::wstring& arguments,
|
||||
DWORD& exitCode,
|
||||
std::string& output) {
|
||||
SECURITY_ATTRIBUTES securityAttributes = {};
|
||||
securityAttributes.nLength = sizeof(securityAttributes);
|
||||
securityAttributes.bInheritHandle = TRUE;
|
||||
|
||||
HANDLE readPipe = nullptr;
|
||||
HANDLE writePipe = nullptr;
|
||||
if (!CreatePipe(&readPipe, &writePipe, &securityAttributes, 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SetHandleInformation(readPipe, HANDLE_FLAG_INHERIT, 0);
|
||||
|
||||
STARTUPINFOW startupInfo = {};
|
||||
startupInfo.cb = sizeof(startupInfo);
|
||||
startupInfo.dwFlags = STARTF_USESTDHANDLES;
|
||||
startupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
|
||||
startupInfo.hStdOutput = writePipe;
|
||||
startupInfo.hStdError = writePipe;
|
||||
|
||||
PROCESS_INFORMATION processInfo = {};
|
||||
std::wstring commandLine = L"\"" + executablePath + L"\" " + arguments;
|
||||
std::vector<wchar_t> commandLineBuffer(commandLine.begin(), commandLine.end());
|
||||
commandLineBuffer.push_back(L'\0');
|
||||
|
||||
const BOOL processCreated = CreateProcessW(nullptr,
|
||||
commandLineBuffer.data(),
|
||||
nullptr,
|
||||
nullptr,
|
||||
TRUE,
|
||||
CREATE_NO_WINDOW,
|
||||
nullptr,
|
||||
nullptr,
|
||||
&startupInfo,
|
||||
&processInfo);
|
||||
|
||||
CloseHandle(writePipe);
|
||||
writePipe = nullptr;
|
||||
|
||||
if (!processCreated) {
|
||||
CloseHandle(readPipe);
|
||||
return false;
|
||||
}
|
||||
|
||||
char buffer[4096] = {};
|
||||
DWORD bytesRead = 0;
|
||||
while (ReadFile(readPipe, buffer, sizeof(buffer), &bytesRead, nullptr) && bytesRead > 0) {
|
||||
output.append(buffer, bytesRead);
|
||||
}
|
||||
|
||||
WaitForSingleObject(processInfo.hProcess, INFINITE);
|
||||
GetExitCodeProcess(processInfo.hProcess, &exitCode);
|
||||
|
||||
CloseHandle(processInfo.hThread);
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(readPipe);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompileGlslToSpirv(const ShaderCompileDesc& desc,
|
||||
const std::string& sourceText,
|
||||
ShaderType type,
|
||||
const std::string& entryPoint,
|
||||
std::vector<uint32_t>& spirvWords,
|
||||
std::string* errorMessage) {
|
||||
std::wstring validatorPath;
|
||||
if (!FindGlslangValidator(validatorPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "glslangValidator.exe was not found. Install Vulkan SDK or add it to PATH.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring tempSourcePath;
|
||||
std::wstring tempOutputPath;
|
||||
if (!CreateTemporaryPath(ShaderSourceExtension(type, false).c_str(), tempSourcePath) ||
|
||||
!CreateTemporaryPath(L".spv", tempOutputPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to allocate temporary shader compiler files.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourceWithMacros = InjectMacrosIntoSource(sourceText, desc.macros);
|
||||
const bool wroteSource = WriteTextFile(tempSourcePath, sourceWithMacros);
|
||||
if (!wroteSource) {
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to write temporary GLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring arguments =
|
||||
L"-V --target-env vulkan1.0 -S " + ShaderStageArgument(type) +
|
||||
L" -e " + WidenAscii(entryPoint.c_str()) +
|
||||
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
|
||||
if (!ranProcess) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to launch glslangValidator.exe.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadedOutput) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = compilerOutput.empty()
|
||||
? "glslangValidator.exe failed to compile GLSL to SPIR-V."
|
||||
: compilerOutput;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Compiled SPIR-V payload size is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
spirvWords.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(spirvWords.data(), bytes.data(), bytes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompileHlslToSpirv(const ShaderCompileDesc& desc,
|
||||
const std::string& sourceText,
|
||||
ShaderType type,
|
||||
const std::string& entryPoint,
|
||||
std::vector<uint32_t>& spirvWords,
|
||||
std::string* errorMessage) {
|
||||
std::wstring validatorPath;
|
||||
if (!FindGlslangValidator(validatorPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "glslangValidator.exe was not found. Install Vulkan SDK or add it to PATH.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring tempSourcePath;
|
||||
std::wstring tempOutputPath;
|
||||
if (!CreateTemporaryPath(ShaderSourceExtension(type, true).c_str(), tempSourcePath) ||
|
||||
!CreateTemporaryPath(L".spv", tempOutputPath)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to allocate temporary shader compiler files.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourceWithMacros = InjectMacrosIntoSource(sourceText, desc.macros);
|
||||
const bool wroteSource = WriteTextFile(tempSourcePath, sourceWithMacros);
|
||||
if (!wroteSource) {
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to write temporary HLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::wstring arguments =
|
||||
L"-D -V --target-env vulkan1.0 -S " + ShaderStageArgument(type) +
|
||||
L" -e " + WidenAscii(entryPoint.c_str()) +
|
||||
L" -o \"" + tempOutputPath + L"\" \"" + tempSourcePath + L"\"";
|
||||
|
||||
DWORD exitCode = 0;
|
||||
std::string compilerOutput;
|
||||
const bool ranProcess = RunProcessAndCapture(validatorPath, arguments, exitCode, compilerOutput);
|
||||
|
||||
std::vector<uint8_t> bytes;
|
||||
const bool loadedOutput = ranProcess && exitCode == 0 && LoadBinaryFile(tempOutputPath, bytes);
|
||||
|
||||
DeleteFileW(tempSourcePath.c_str());
|
||||
DeleteFileW(tempOutputPath.c_str());
|
||||
|
||||
if (!ranProcess) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to launch glslangValidator.exe.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadedOutput) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = compilerOutput.empty()
|
||||
? "glslangValidator.exe failed to compile HLSL to SPIR-V."
|
||||
: compilerOutput;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Compiled SPIR-V payload size is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
spirvWords.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(spirvWords.data(), bytes.data(), bytes.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool CompileVulkanShader(const ShaderCompileDesc& desc,
|
||||
VulkanCompiledShader& outShader,
|
||||
std::string* errorMessage) {
|
||||
outShader = {};
|
||||
outShader.entryPoint = NarrowAscii(desc.entryPoint);
|
||||
if (outShader.entryPoint.empty()) {
|
||||
outShader.entryPoint = "main";
|
||||
}
|
||||
|
||||
if (desc.source.empty() && desc.fileName.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "No shader source or file name was provided.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsSpirvInput(desc)) {
|
||||
std::vector<uint8_t> bytes;
|
||||
if (!desc.source.empty()) {
|
||||
bytes = desc.source;
|
||||
} else if (!LoadBinaryFile(std::filesystem::path(desc.fileName), bytes)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to read SPIR-V shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (bytes.empty() || (bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "SPIR-V payload size is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
outShader.spirvWords.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(outShader.spirvWords.data(), bytes.data(), bytes.size());
|
||||
|
||||
if (outShader.spirvWords.empty() || outShader.spirvWords[0] != kSpirvMagic) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "SPIR-V payload does not contain a valid SPIR-V header.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string parsedEntryPoint;
|
||||
if (!ParseSpirvMetadata(outShader.spirvWords.data(),
|
||||
outShader.spirvWords.size(),
|
||||
outShader.type,
|
||||
parsedEntryPoint)) {
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
if (!TryResolveShaderTypeFromTarget(profile.c_str(), outShader.type)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to parse SPIR-V shader metadata.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
} else if (desc.entryPoint.empty() && !parsedEntryPoint.empty()) {
|
||||
outShader.entryPoint = parsedEntryPoint;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string sourceText;
|
||||
if (!desc.source.empty()) {
|
||||
sourceText.assign(reinterpret_cast<const char*>(desc.source.data()), desc.source.size());
|
||||
} else if (!LoadTextFile(std::filesystem::path(desc.fileName), sourceText)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = IsHlslInput(desc)
|
||||
? "Failed to read HLSL shader file."
|
||||
: "Failed to read GLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceText.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = IsHlslInput(desc)
|
||||
? "HLSL shader source is empty."
|
||||
: "GLSL shader source is empty.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!ResolveShaderType(desc, sourceText, outShader.type)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "Failed to resolve Vulkan shader stage from profile, file extension, or source text.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsHlslInput(desc)) {
|
||||
if (!CompileHlslToSpirv(desc,
|
||||
sourceText,
|
||||
outShader.type,
|
||||
outShader.entryPoint,
|
||||
outShader.spirvWords,
|
||||
errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!CompileGlslToSpirv(desc,
|
||||
sourceText,
|
||||
outShader.type,
|
||||
outShader.entryPoint,
|
||||
outShader.spirvWords,
|
||||
errorMessage)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderType parsedType = outShader.type;
|
||||
std::string parsedEntryPoint;
|
||||
if (ParseSpirvMetadata(outShader.spirvWords.data(),
|
||||
outShader.spirvWords.size(),
|
||||
parsedType,
|
||||
parsedEntryPoint)) {
|
||||
outShader.type = parsedType;
|
||||
if (desc.entryPoint.empty() && !parsedEntryPoint.empty()) {
|
||||
outShader.entryPoint = parsedEntryPoint;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return CompileSpirvShader(desc, SpirvTargetEnvironment::Vulkan, outShader, errorMessage);
|
||||
}
|
||||
|
||||
} // namespace RHI
|
||||
|
||||
Reference in New Issue
Block a user