// Copyright 2022 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoCommon/Spirv.h" #include #include #include "Common/FileUtil.h" #include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/StringUtil.h" #include "Common/Version.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" namespace { bool InitializeGlslang() { static bool glslang_initialized = false; if (glslang_initialized) return true; if (!glslang::InitializeProcess()) { PanicAlertFmt("Failed to initialize glslang shader compiler"); return false; } std::atexit([] { glslang::FinalizeProcess(); }); glslang_initialized = true; return true; } const TBuiltInResource* GetCompilerResourceLimits() { static const TBuiltInResource default_resource = { /* .MaxLights = */ 32, /* .MaxClipPlanes = */ 6, /* .MaxTextureUnits = */ 32, /* .MaxTextureCoords = */ 32, /* .MaxVertexAttribs = */ 64, /* .MaxVertexUniformComponents = */ 4096, /* .MaxVaryingFloats = */ 64, /* .MaxVertexTextureImageUnits = */ 32, /* .MaxCombinedTextureImageUnits = */ 80, /* .MaxTextureImageUnits = */ 32, /* .MaxFragmentUniformComponents = */ 4096, /* .MaxDrawBuffers = */ 32, /* .MaxVertexUniformVectors = */ 128, /* .MaxVaryingVectors = */ 8, /* .MaxFragmentUniformVectors = */ 16, /* .MaxVertexOutputVectors = */ 16, /* .MaxFragmentInputVectors = */ 15, /* .MinProgramTexelOffset = */ -8, /* .MaxProgramTexelOffset = */ 7, /* .MaxClipDistances = */ 8, /* .MaxComputeWorkGroupCountX = */ 65535, /* .MaxComputeWorkGroupCountY = */ 65535, /* .MaxComputeWorkGroupCountZ = */ 65535, /* .MaxComputeWorkGroupSizeX = */ 1024, /* .MaxComputeWorkGroupSizeY = */ 1024, /* .MaxComputeWorkGroupSizeZ = */ 64, /* .MaxComputeUniformComponents = */ 1024, /* .MaxComputeTextureImageUnits = */ 16, /* .MaxComputeImageUniforms = */ 8, /* .MaxComputeAtomicCounters = */ 8, /* .MaxComputeAtomicCounterBuffers = */ 1, /* .MaxVaryingComponents = */ 60, /* .MaxVertexOutputComponents = */ 64, /* .MaxGeometryInputComponents = */ 64, /* .MaxGeometryOutputComponents = */ 128, /* .MaxFragmentInputComponents = */ 128, /* .MaxImageUnits = */ 8, /* .MaxCombinedImageUnitsAndFragmentOutputs = */ 8, /* .MaxCombinedShaderOutputResources = */ 8, /* .MaxImageSamples = */ 0, /* .MaxVertexImageUniforms = */ 0, /* .MaxTessControlImageUniforms = */ 0, /* .MaxTessEvaluationImageUniforms = */ 0, /* .MaxGeometryImageUniforms = */ 0, /* .MaxFragmentImageUniforms = */ 8, /* .MaxCombinedImageUniforms = */ 8, /* .MaxGeometryTextureImageUnits = */ 16, /* .MaxGeometryOutputVertices = */ 256, /* .MaxGeometryTotalOutputComponents = */ 1024, /* .MaxGeometryUniformComponents = */ 1024, /* .MaxGeometryVaryingComponents = */ 64, /* .MaxTessControlInputComponents = */ 128, /* .MaxTessControlOutputComponents = */ 128, /* .MaxTessControlTextureImageUnits = */ 16, /* .MaxTessControlUniformComponents = */ 1024, /* .MaxTessControlTotalOutputComponents = */ 4096, /* .MaxTessEvaluationInputComponents = */ 128, /* .MaxTessEvaluationOutputComponents = */ 128, /* .MaxTessEvaluationTextureImageUnits = */ 16, /* .MaxTessEvaluationUniformComponents = */ 1024, /* .MaxTessPatchComponents = */ 120, /* .MaxPatchVertices = */ 32, /* .MaxTessGenLevel = */ 64, /* .MaxViewports = */ 16, /* .MaxVertexAtomicCounters = */ 0, /* .MaxTessControlAtomicCounters = */ 0, /* .MaxTessEvaluationAtomicCounters = */ 0, /* .MaxGeometryAtomicCounters = */ 0, /* .MaxFragmentAtomicCounters = */ 8, /* .MaxCombinedAtomicCounters = */ 8, /* .MaxAtomicCounterBindings = */ 1, /* .MaxVertexAtomicCounterBuffers = */ 0, /* .MaxTessControlAtomicCounterBuffers = */ 0, /* .MaxTessEvaluationAtomicCounterBuffers = */ 0, /* .MaxGeometryAtomicCounterBuffers = */ 0, /* .MaxFragmentAtomicCounterBuffers = */ 1, /* .MaxCombinedAtomicCounterBuffers = */ 1, /* .MaxAtomicCounterBufferSize = */ 16384, /* .MaxTransformFeedbackBuffers = */ 4, /* .MaxTransformFeedbackInterleavedComponents = */ 64, /* .MaxCullDistances = */ 8, /* .MaxCombinedClipAndCullDistances = */ 8, /* .MaxSamples = */ 4, /* .maxMeshOutputVerticesNV = */ 256, /* .maxMeshOutputPrimitivesNV = */ 512, /* .maxMeshWorkGroupSizeX_NV = */ 32, /* .maxMeshWorkGroupSizeY_NV = */ 1, /* .maxMeshWorkGroupSizeZ_NV = */ 1, /* .maxTaskWorkGroupSizeX_NV = */ 32, /* .maxTaskWorkGroupSizeY_NV = */ 1, /* .maxTaskWorkGroupSizeZ_NV = */ 1, /* .maxMeshViewCountNV = */ 4, /* .maxMeshOutputVerticesEXT = */ 256, /* .maxMeshOutputPrimitivesEXT = */ 256, /* .maxMeshWorkGroupSizeX_EXT = */ 128, /* .maxMeshWorkGroupSizeY_EXT = */ 128, /* .maxMeshWorkGroupSizeZ_EXT = */ 128, /* .maxTaskWorkGroupSizeX_EXT = */ 128, /* .maxTaskWorkGroupSizeY_EXT = */ 128, /* .maxTaskWorkGroupSizeZ_EXT = */ 128, /* .maxMeshViewCountEXT = */ 4, /* .maxDualSourceDrawBuffersEXT = */ 1, /* .limits = */ { /* .nonInductiveForLoops = */ 1, /* .whileLoops = */ 1, /* .doWhileLoops = */ 1, /* .generalUniformIndexing = */ 1, /* .generalAttributeMatrixVectorIndexing = */ 1, /* .generalVaryingIndexing = */ 1, /* .generalSamplerIndexing = */ 1, /* .generalVariableIndexing = */ 1, /* .generalConstantMatrixVectorIndexing = */ 1, }}; return &default_resource; } std::optional CompileShaderToSPV(EShLanguage stage, APIType api_type, glslang::EShTargetLanguageVersion language_version, const char* stage_filename, std::string_view source) { if (!InitializeGlslang()) return std::nullopt; std::unique_ptr shader = std::make_unique(stage); std::unique_ptr program; glslang::TShader::ForbidIncluder includer; EProfile profile = ECoreProfile; EShMessages messages = static_cast(EShMsgDefault | EShMsgSpvRules); if (api_type == APIType::Vulkan || api_type == APIType::Metal) messages = static_cast(messages | EShMsgVulkanRules); int default_version = 450; const char* pass_source_code = source.data(); int pass_source_code_length = static_cast(source.size()); shader->setEnvTarget(glslang::EShTargetSpv, language_version); shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1); auto DumpBadShader = [&](const char* msg) { static int counter = 0; std::string filename = VideoBackendBase::BadShaderFilename(stage_filename, counter++); std::ofstream stream; File::OpenFStream(stream, filename, std::ios_base::out); if (stream.good()) { stream << source << std::endl; stream << msg << std::endl; stream << "Shader Info Log:" << std::endl; stream << shader->getInfoLog() << std::endl; stream << shader->getInfoDebugLog() << std::endl; if (program) { stream << "Program Info Log:" << std::endl; stream << program->getInfoLog() << std::endl; stream << program->getInfoDebugLog() << std::endl; } } stream << "\n"; stream << "Dolphin Version: " + Common::GetScmRevStr() + "\n"; stream << "Video Backend: " + g_video_backend->GetDisplayName(); stream.close(); PanicAlertFmt("{} (written to {})\nDebug info:\n{}", msg, filename, shader->getInfoLog()); }; if (!shader->parse(GetCompilerResourceLimits(), default_version, profile, false, true, messages, includer)) { DumpBadShader("Failed to parse shader"); return std::nullopt; } // Even though there's only a single shader, we still need to link it to generate SPV program = std::make_unique(); program->addShader(shader.get()); if (!program->link(messages)) { DumpBadShader("Failed to link program"); return std::nullopt; } glslang::TIntermediate* intermediate = program->getIntermediate(stage); if (!intermediate) { DumpBadShader("Failed to generate SPIR-V"); return std::nullopt; } SPIRV::CodeVector out_code; spv::SpvBuildLogger logger; glslang::SpvOptions options; if (g_ActiveConfig.bEnableValidationLayer) { // Attach the source code to the SPIR-V for tools like RenderDoc. shader->setSourceFile(stage_filename); shader->addSourceText(pass_source_code, pass_source_code_length); options.generateDebugInfo = true; options.disableOptimizer = true; options.optimizeSize = false; options.disassemble = false; options.validate = true; } else { options.disableOptimizer = false; options.stripDebugInfo = true; } glslang::GlslangToSpv(*intermediate, out_code, &logger, &options); // Write out messages // Temporary: skip if it contains "Warning, version 450 is not yet complete; most version-specific // features are present, but some are missing." if (strlen(shader->getInfoLog()) > 108) WARN_LOG_FMT(VIDEO, "Shader info log: {}", shader->getInfoLog()); if (strlen(shader->getInfoDebugLog()) > 0) WARN_LOG_FMT(VIDEO, "Shader debug info log: {}", shader->getInfoDebugLog()); if (strlen(program->getInfoLog()) > 25) WARN_LOG_FMT(VIDEO, "Program info log: {}", program->getInfoLog()); if (strlen(program->getInfoDebugLog()) > 0) WARN_LOG_FMT(VIDEO, "Program debug info log: {}", program->getInfoDebugLog()); const std::string spv_messages = logger.getAllMessages(); if (!spv_messages.empty()) WARN_LOG_FMT(VIDEO, "SPIR-V conversion messages: {}", spv_messages); return out_code; } } // namespace namespace SPIRV { std::optional CompileVertexShader(std::string_view source_code, APIType api_type, glslang::EShTargetLanguageVersion language_version) { return CompileShaderToSPV(EShLangVertex, api_type, language_version, "vs", source_code); } std::optional CompileGeometryShader(std::string_view source_code, APIType api_type, glslang::EShTargetLanguageVersion language_version) { return CompileShaderToSPV(EShLangGeometry, api_type, language_version, "gs", source_code); } std::optional CompileFragmentShader(std::string_view source_code, APIType api_type, glslang::EShTargetLanguageVersion language_version) { return CompileShaderToSPV(EShLangFragment, api_type, language_version, "ps", source_code); } std::optional CompileComputeShader(std::string_view source_code, APIType api_type, glslang::EShTargetLanguageVersion language_version) { return CompileShaderToSPV(EShLangCompute, api_type, language_version, "cs", source_code); } } // namespace SPIRV