Add Vulkan GLSL shader compilation path
This commit is contained in:
@@ -157,6 +157,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanTexture.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanSampler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanShader.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanShaderCompiler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDescriptorPool.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanDescriptorSet.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/RHI/Vulkan/VulkanPipelineLayout.h
|
||||
@@ -174,6 +175,7 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanTexture.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanSampler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanShader.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanShaderCompiler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDescriptorPool.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanDescriptorSet.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/RHI/Vulkan/VulkanPipelineLayout.cpp
|
||||
|
||||
@@ -8,12 +8,22 @@
|
||||
|
||||
#include "XCEngine/RHI/RHIEnums.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <cmath>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace RHI {
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline std::wstring WidenAscii(const char* value) {
|
||||
if (value == nullptr) {
|
||||
return {};
|
||||
@@ -23,6 +33,42 @@ inline std::wstring WidenAscii(const char* value) {
|
||||
return std::wstring(ascii.begin(), ascii.end());
|
||||
}
|
||||
|
||||
inline 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;
|
||||
}
|
||||
|
||||
inline VkFormat ToVulkanFormat(Format format) {
|
||||
switch (format) {
|
||||
case Format::R8G8B8A8_UNorm:
|
||||
|
||||
23
engine/include/XCEngine/RHI/Vulkan/VulkanShaderCompiler.h
Normal file
23
engine/include/XCEngine/RHI/Vulkan/VulkanShaderCompiler.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "XCEngine/RHI/RHITypes.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;
|
||||
};
|
||||
|
||||
bool CompileVulkanShader(const ShaderCompileDesc& desc,
|
||||
VulkanCompiledShader& outShader,
|
||||
std::string* errorMessage = nullptr);
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "XCEngine/RHI/Vulkan/VulkanResourceView.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanSampler.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanShader.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanShaderCompiler.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanSwapChain.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanTexture.h"
|
||||
|
||||
@@ -27,15 +28,6 @@ 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 ResolveVendorName(uint32_t vendorId) {
|
||||
switch (vendorId) {
|
||||
case 0x10DE: return L"NVIDIA";
|
||||
@@ -645,27 +637,13 @@ RHICommandQueue* VulkanDevice::CreateCommandQueue(const CommandQueueDesc& desc)
|
||||
|
||||
RHIShader* VulkanDevice::CreateShader(const ShaderCompileDesc& desc) {
|
||||
auto* shader = new VulkanShader(m_device);
|
||||
bool success = false;
|
||||
|
||||
const std::string entryPoint = NarrowAscii(desc.entryPoint);
|
||||
const std::string profile = NarrowAscii(desc.profile);
|
||||
|
||||
const bool isSpirvSource = desc.sourceLanguage == ShaderLanguage::SPIRV;
|
||||
const bool isSpirvFile = !desc.fileName.empty() &&
|
||||
std::filesystem::path(desc.fileName).extension() == L".spv";
|
||||
|
||||
if (!desc.source.empty() && isSpirvSource) {
|
||||
success = shader->Compile(
|
||||
desc.source.data(),
|
||||
desc.source.size(),
|
||||
entryPoint.empty() ? nullptr : entryPoint.c_str(),
|
||||
profile.empty() ? nullptr : profile.c_str());
|
||||
} else if (!desc.fileName.empty() && (isSpirvSource || isSpirvFile)) {
|
||||
success = shader->CompileFromFile(
|
||||
desc.fileName.c_str(),
|
||||
entryPoint.empty() ? nullptr : entryPoint.c_str(),
|
||||
profile.empty() ? nullptr : profile.c_str());
|
||||
}
|
||||
VulkanCompiledShader compiledShader = {};
|
||||
const bool success = CompileVulkanShader(desc, compiledShader, nullptr) &&
|
||||
shader->Compile(
|
||||
compiledShader.spirvWords.data(),
|
||||
compiledShader.spirvWords.size() * sizeof(uint32_t),
|
||||
compiledShader.entryPoint.empty() ? nullptr : compiledShader.entryPoint.c_str(),
|
||||
nullptr);
|
||||
|
||||
if (success) {
|
||||
return shader;
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
#include "XCEngine/RHI/Vulkan/VulkanDevice.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanPipelineLayout.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanShader.h"
|
||||
#include "XCEngine/RHI/Vulkan/VulkanShaderCompiler.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
@@ -16,57 +14,6 @@ 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;
|
||||
}
|
||||
|
||||
bool LoadSpirvBytes(const ShaderCompileDesc& desc, std::vector<uint32_t>& words, std::string& entryPoint) {
|
||||
entryPoint = NarrowAscii(desc.entryPoint);
|
||||
if (entryPoint.empty()) {
|
||||
entryPoint = "main";
|
||||
}
|
||||
|
||||
if (desc.sourceLanguage != ShaderLanguage::SPIRV) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<char> bytes;
|
||||
if (!desc.source.empty()) {
|
||||
bytes.assign(desc.source.begin(), desc.source.end());
|
||||
} else if (!desc.fileName.empty()) {
|
||||
std::ifstream file(std::filesystem::path(desc.fileName), std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize fileSize = file.tellg();
|
||||
if (fileSize <= 0 || (fileSize % 4) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bytes.resize(static_cast<size_t>(fileSize));
|
||||
file.seekg(0, std::ios::beg);
|
||||
if (!file.read(bytes.data(), fileSize)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((bytes.size() % sizeof(uint32_t)) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
words.resize(bytes.size() / sizeof(uint32_t));
|
||||
std::memcpy(words.data(), bytes.data(), bytes.size());
|
||||
return !words.empty();
|
||||
}
|
||||
|
||||
bool HasShaderPayload(const ShaderCompileDesc& desc) {
|
||||
return !desc.source.empty() || !desc.fileName.empty();
|
||||
}
|
||||
@@ -154,17 +101,17 @@ bool VulkanPipelineState::EnsurePipelineLayout(const GraphicsPipelineDesc& desc)
|
||||
}
|
||||
|
||||
bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& desc) {
|
||||
std::vector<uint32_t> vertexWords;
|
||||
std::vector<uint32_t> fragmentWords;
|
||||
std::string vertexEntryPoint;
|
||||
std::string fragmentEntryPoint;
|
||||
if (!LoadSpirvBytes(desc.vertexShader, vertexWords, vertexEntryPoint) ||
|
||||
!LoadSpirvBytes(desc.fragmentShader, fragmentWords, fragmentEntryPoint)) {
|
||||
VulkanCompiledShader vertexShader = {};
|
||||
VulkanCompiledShader fragmentShader = {};
|
||||
if (!CompileVulkanShader(desc.vertexShader, vertexShader, nullptr) ||
|
||||
!CompileVulkanShader(desc.fragmentShader, fragmentShader, nullptr) ||
|
||||
vertexShader.type != ShaderType::Vertex ||
|
||||
fragmentShader.type != ShaderType::Fragment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const VkShaderModule vertexModule = CreateShaderModule(m_device, vertexWords);
|
||||
const VkShaderModule fragmentModule = CreateShaderModule(m_device, fragmentWords);
|
||||
const VkShaderModule vertexModule = CreateShaderModule(m_device, vertexShader.spirvWords);
|
||||
const VkShaderModule fragmentModule = CreateShaderModule(m_device, fragmentShader.spirvWords);
|
||||
if (vertexModule == VK_NULL_HANDLE || fragmentModule == VK_NULL_HANDLE) {
|
||||
if (vertexModule != VK_NULL_HANDLE) {
|
||||
vkDestroyShaderModule(m_device, vertexModule, nullptr);
|
||||
@@ -264,12 +211,12 @@ bool VulkanPipelineState::CreateGraphicsPipeline(const GraphicsPipelineDesc& des
|
||||
shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
|
||||
shaderStages[0].module = vertexModule;
|
||||
shaderStages[0].pName = vertexEntryPoint.c_str();
|
||||
shaderStages[0].pName = vertexShader.entryPoint.c_str();
|
||||
|
||||
shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
|
||||
shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
shaderStages[1].module = fragmentModule;
|
||||
shaderStages[1].pName = fragmentEntryPoint.c_str();
|
||||
shaderStages[1].pName = fragmentShader.entryPoint.c_str();
|
||||
|
||||
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
|
||||
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
|
||||
|
||||
@@ -136,27 +136,7 @@ bool VulkanShader::InitializeFromSpirvWords(const uint32_t* words, size_t wordCo
|
||||
}
|
||||
|
||||
bool VulkanShader::ResolveShaderTypeFromTarget(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) {
|
||||
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, "cs_") != nullptr || std::strcmp(target, "cs") == 0) {
|
||||
type = ShaderType::Compute;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return TryResolveShaderTypeFromTarget(target, type);
|
||||
}
|
||||
|
||||
bool VulkanShader::ResolveShaderTypeFromSpirv(const uint32_t* words, size_t wordCount, ShaderType& type, std::string& entryPoint) {
|
||||
|
||||
613
engine/src/RHI/Vulkan/VulkanShaderCompiler.cpp
Normal file
613
engine/src/RHI/Vulkan/VulkanShaderCompiler.cpp
Normal file
@@ -0,0 +1,613 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
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(ShaderStageExtension(type).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;
|
||||
}
|
||||
|
||||
} // 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;
|
||||
}
|
||||
|
||||
if (IsHlslInput(desc)) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "The Vulkan backend currently supports GLSL or SPIR-V inputs, not HLSL source.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = "Failed to read GLSL shader file.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceText.empty()) {
|
||||
if (errorMessage != nullptr) {
|
||||
*errorMessage = "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 (!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;
|
||||
}
|
||||
|
||||
} // namespace RHI
|
||||
} // namespace XCEngine
|
||||
@@ -1,10 +1,13 @@
|
||||
#if defined(XCENGINE_SUPPORT_VULKAN)
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
|
||||
#include "XCEngine/RHI/RHICommandList.h"
|
||||
@@ -28,6 +31,17 @@ using namespace XCEngine::RHI;
|
||||
|
||||
namespace {
|
||||
|
||||
std::wstring ResolveShaderPath(const wchar_t* relativePath) {
|
||||
wchar_t exePath[MAX_PATH] = {};
|
||||
const DWORD length = GetModuleFileNameW(nullptr, exePath, MAX_PATH);
|
||||
std::filesystem::path rootPath =
|
||||
length > 0 ? std::filesystem::path(exePath).parent_path() : std::filesystem::current_path();
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
rootPath = rootPath.parent_path();
|
||||
}
|
||||
return (rootPath / relativePath).wstring();
|
||||
}
|
||||
|
||||
VkImageLayout ToImageLayout(ResourceStates state) {
|
||||
switch (state) {
|
||||
case ResourceStates::RenderTarget:
|
||||
@@ -206,6 +220,21 @@ protected:
|
||||
return m_device->CreateShader(shaderDesc);
|
||||
}
|
||||
|
||||
RHIShader* CreateWriteRedComputeShaderFromGlsl() const {
|
||||
static const char* computeSource = R"(#version 450
|
||||
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
|
||||
layout(set = 0, binding = 0, rgba8) uniform writeonly image2D uImage;
|
||||
void main() {
|
||||
imageStore(uImage, ivec2(0, 0), vec4(1.0, 0.0, 0.0, 1.0));
|
||||
}
|
||||
)";
|
||||
|
||||
ShaderCompileDesc shaderDesc = {};
|
||||
shaderDesc.sourceLanguage = ShaderLanguage::GLSL;
|
||||
shaderDesc.source.assign(computeSource, computeSource + std::strlen(computeSource));
|
||||
return m_device->CreateShader(shaderDesc);
|
||||
}
|
||||
|
||||
void SubmitAndWait(RHICommandList* commandList) {
|
||||
ASSERT_NE(commandList, nullptr);
|
||||
commandList->Close();
|
||||
@@ -464,6 +493,97 @@ TEST_F(VulkanGraphicsFixture, CreateShaderFromSpirvProducesValidComputeShader) {
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, CreateShaderFromGlslSourceProducesValidVertexShader) {
|
||||
static const char* vertexSource = R"(#version 450
|
||||
layout(location = 0) in vec4 aPosition;
|
||||
void main() {
|
||||
gl_Position = aPosition;
|
||||
}
|
||||
)";
|
||||
|
||||
ShaderCompileDesc shaderDesc = {};
|
||||
shaderDesc.sourceLanguage = ShaderLanguage::GLSL;
|
||||
shaderDesc.profile = L"vs_4_50";
|
||||
shaderDesc.source.assign(vertexSource, vertexSource + std::strlen(vertexSource));
|
||||
|
||||
RHIShader* shader = m_device->CreateShader(shaderDesc);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
EXPECT_TRUE(shader->IsValid());
|
||||
EXPECT_EQ(shader->GetType(), ShaderType::Vertex);
|
||||
EXPECT_NE(shader->GetNativeHandle(), nullptr);
|
||||
shader->Shutdown();
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, CreateShaderFromGlslFileProducesValidVertexShader) {
|
||||
ShaderCompileDesc shaderDesc = {};
|
||||
shaderDesc.fileName = ResolveShaderPath(L"tests/RHI/integration/triangle/Res/Shader/triangle_vulkan.vert");
|
||||
shaderDesc.entryPoint = L"main";
|
||||
shaderDesc.profile = L"vs_4_50";
|
||||
|
||||
RHIShader* shader = m_device->CreateShader(shaderDesc);
|
||||
ASSERT_NE(shader, nullptr);
|
||||
EXPECT_TRUE(shader->IsValid());
|
||||
EXPECT_EQ(shader->GetType(), ShaderType::Vertex);
|
||||
EXPECT_NE(shader->GetNativeHandle(), nullptr);
|
||||
shader->Shutdown();
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, CreateShaderFromGlslSourceInfersComputeShader) {
|
||||
RHIShader* shader = CreateWriteRedComputeShaderFromGlsl();
|
||||
ASSERT_NE(shader, nullptr);
|
||||
EXPECT_TRUE(shader->IsValid());
|
||||
EXPECT_EQ(shader->GetType(), ShaderType::Compute);
|
||||
EXPECT_NE(shader->GetNativeHandle(), nullptr);
|
||||
shader->Shutdown();
|
||||
delete shader;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, CreateGraphicsPipelineFromGlslShadersProducesValidPipeline) {
|
||||
static const char* vertexSource = R"(#version 450
|
||||
layout(location = 0) in vec4 aPosition;
|
||||
void main() {
|
||||
gl_Position = aPosition;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* fragmentSource = R"(#version 450
|
||||
layout(location = 0) out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.topologyType = static_cast<uint32_t>(PrimitiveTopologyType::Triangle);
|
||||
pipelineDesc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
pipelineDesc.depthStencilFormat = static_cast<uint32_t>(Format::Unknown);
|
||||
|
||||
InputElementDesc position = {};
|
||||
position.semanticName = "POSITION";
|
||||
position.semanticIndex = 0;
|
||||
position.format = static_cast<uint32_t>(Format::R32G32B32A32_Float);
|
||||
position.inputSlot = 0;
|
||||
position.alignedByteOffset = 0;
|
||||
pipelineDesc.inputLayout.elements.push_back(position);
|
||||
|
||||
pipelineDesc.vertexShader.sourceLanguage = ShaderLanguage::GLSL;
|
||||
pipelineDesc.vertexShader.profile = L"vs_4_50";
|
||||
pipelineDesc.vertexShader.source.assign(vertexSource, vertexSource + std::strlen(vertexSource));
|
||||
|
||||
pipelineDesc.fragmentShader.sourceLanguage = ShaderLanguage::GLSL;
|
||||
pipelineDesc.fragmentShader.profile = L"fs_4_50";
|
||||
pipelineDesc.fragmentShader.source.assign(fragmentSource, fragmentSource + std::strlen(fragmentSource));
|
||||
|
||||
RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc);
|
||||
ASSERT_NE(pipelineState, nullptr);
|
||||
EXPECT_TRUE(pipelineState->IsValid());
|
||||
EXPECT_NE(pipelineState->GetNativeHandle(), nullptr);
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, CreateUnorderedAccessViewProducesValidView) {
|
||||
RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4));
|
||||
ASSERT_NE(texture, nullptr);
|
||||
@@ -567,6 +687,90 @@ TEST_F(VulkanGraphicsFixture, DispatchWritesUavTexture) {
|
||||
delete texture;
|
||||
}
|
||||
|
||||
TEST_F(VulkanGraphicsFixture, DispatchWritesUavTextureWithGlslComputeShader) {
|
||||
RHITexture* texture = m_device->CreateTexture(CreateColorTextureDesc(4, 4));
|
||||
ASSERT_NE(texture, nullptr);
|
||||
|
||||
ResourceViewDesc uavDesc = {};
|
||||
uavDesc.format = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
|
||||
uavDesc.dimension = ResourceViewDimension::Texture2D;
|
||||
RHIResourceView* uav = m_device->CreateUnorderedAccessView(texture, uavDesc);
|
||||
ASSERT_NE(uav, nullptr);
|
||||
|
||||
DescriptorPoolDesc poolDesc = {};
|
||||
poolDesc.type = DescriptorHeapType::CBV_SRV_UAV;
|
||||
poolDesc.descriptorCount = 1;
|
||||
poolDesc.shaderVisible = true;
|
||||
RHIDescriptorPool* pool = m_device->CreateDescriptorPool(poolDesc);
|
||||
ASSERT_NE(pool, nullptr);
|
||||
|
||||
DescriptorSetLayoutBinding uavBinding = {};
|
||||
uavBinding.binding = 0;
|
||||
uavBinding.type = static_cast<uint32_t>(DescriptorType::UAV);
|
||||
uavBinding.count = 1;
|
||||
uavBinding.visibility = static_cast<uint32_t>(ShaderVisibility::All);
|
||||
|
||||
DescriptorSetLayoutDesc setLayout = {};
|
||||
setLayout.bindings = &uavBinding;
|
||||
setLayout.bindingCount = 1;
|
||||
|
||||
RHIPipelineLayoutDesc pipelineLayoutDesc = {};
|
||||
pipelineLayoutDesc.setLayouts = &setLayout;
|
||||
pipelineLayoutDesc.setLayoutCount = 1;
|
||||
RHIPipelineLayout* pipelineLayout = m_device->CreatePipelineLayout(pipelineLayoutDesc);
|
||||
ASSERT_NE(pipelineLayout, nullptr);
|
||||
|
||||
RHIDescriptorSet* descriptorSet = pool->AllocateSet(setLayout);
|
||||
ASSERT_NE(descriptorSet, nullptr);
|
||||
descriptorSet->Update(0, uav);
|
||||
|
||||
GraphicsPipelineDesc pipelineDesc = {};
|
||||
pipelineDesc.pipelineLayout = pipelineLayout;
|
||||
RHIPipelineState* pipelineState = m_device->CreatePipelineState(pipelineDesc);
|
||||
ASSERT_NE(pipelineState, nullptr);
|
||||
|
||||
RHIShader* shader = CreateWriteRedComputeShaderFromGlsl();
|
||||
ASSERT_NE(shader, nullptr);
|
||||
pipelineState->SetComputeShader(shader);
|
||||
EXPECT_TRUE(pipelineState->HasComputeShader());
|
||||
EXPECT_EQ(pipelineState->GetType(), PipelineType::Compute);
|
||||
|
||||
RHICommandList* commandList = CreateCommandList();
|
||||
ASSERT_NE(commandList, nullptr);
|
||||
|
||||
commandList->Reset();
|
||||
commandList->TransitionBarrier(uav, ResourceStates::Common, ResourceStates::UnorderedAccess);
|
||||
commandList->SetPipelineState(pipelineState);
|
||||
RHIDescriptorSet* descriptorSets[] = { descriptorSet };
|
||||
commandList->SetComputeDescriptorSets(0, 1, descriptorSets, pipelineLayout);
|
||||
commandList->Dispatch(1, 1, 1);
|
||||
SubmitAndWait(commandList);
|
||||
|
||||
const std::vector<uint8_t> pixels = ReadTextureRgba8(static_cast<VulkanTexture*>(texture));
|
||||
ASSERT_GE(pixels.size(), 4u);
|
||||
EXPECT_EQ(pixels[0], 255u);
|
||||
EXPECT_EQ(pixels[1], 0u);
|
||||
EXPECT_EQ(pixels[2], 0u);
|
||||
EXPECT_EQ(pixels[3], 255u);
|
||||
|
||||
commandList->Shutdown();
|
||||
delete commandList;
|
||||
shader->Shutdown();
|
||||
delete shader;
|
||||
pipelineState->Shutdown();
|
||||
delete pipelineState;
|
||||
descriptorSet->Shutdown();
|
||||
delete descriptorSet;
|
||||
pipelineLayout->Shutdown();
|
||||
delete pipelineLayout;
|
||||
pool->Shutdown();
|
||||
delete pool;
|
||||
uav->Shutdown();
|
||||
delete uav;
|
||||
texture->Shutdown();
|
||||
delete texture;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user