mirror of
https://github.com/ZDoom/ZDRay.git
synced 2024-11-25 21:31:32 +00:00
2238 lines
81 KiB
C++
2238 lines
81 KiB
C++
//
|
|
// Copyright (C) 2002-2005 3Dlabs Inc. Ltd.
|
|
// Copyright (C) 2013-2016 LunarG, Inc.
|
|
// Copyright (C) 2015-2020 Google, Inc.
|
|
//
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions
|
|
// are met:
|
|
//
|
|
// Redistributions of source code must retain the above copyright
|
|
// notice, this list of conditions and the following disclaimer.
|
|
//
|
|
// Redistributions in binary form must reproduce the above
|
|
// copyright notice, this list of conditions and the following
|
|
// disclaimer in the documentation and/or other materials provided
|
|
// with the distribution.
|
|
//
|
|
// Neither the name of 3Dlabs Inc. Ltd. nor the names of its
|
|
// contributors may be used to endorse or promote products derived
|
|
// from this software without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
// COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
|
|
//
|
|
// Implement the top-level of interface to the compiler/linker,
|
|
// as defined in ShaderLang.h
|
|
// This is the platform independent interface between an OGL driver
|
|
// and the shading language compiler/linker.
|
|
//
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <memory>
|
|
#include "SymbolTable.h"
|
|
#include "ParseHelper.h"
|
|
#include "Scan.h"
|
|
#include "ScanContext.h"
|
|
|
|
#ifdef ENABLE_HLSL
|
|
#include "../HLSL/hlslParseHelper.h"
|
|
#include "../HLSL/hlslParseables.h"
|
|
#include "../HLSL/hlslScanContext.h"
|
|
#endif
|
|
|
|
#include "../Include/ShHandle.h"
|
|
#include "../../OGLCompilersDLL/InitializeDll.h"
|
|
|
|
#include "preprocessor/PpContext.h"
|
|
|
|
#define SH_EXPORTING
|
|
#include "../Public/ShaderLang.h"
|
|
#include "reflection.h"
|
|
#include "iomapper.h"
|
|
#include "Initialize.h"
|
|
|
|
// TODO: this really shouldn't be here, it is only because of the trial addition
|
|
// of printing pre-processed tokens, which requires knowing the string literal
|
|
// token to print ", but none of that seems appropriate for this file.
|
|
#include "preprocessor/PpTokens.h"
|
|
|
|
// Build-time generated includes
|
|
#include "../Include/build_info.h"
|
|
|
|
namespace { // anonymous namespace for file-local functions and symbols
|
|
|
|
// Total number of successful initializers of glslang: a refcount
|
|
// Shared global; access should be protected by a global mutex/critical section.
|
|
int NumberOfClients = 0;
|
|
|
|
using namespace glslang;
|
|
|
|
// Create a language specific version of parseables.
|
|
TBuiltInParseables* CreateBuiltInParseables(TInfoSink& infoSink, EShSource source)
|
|
{
|
|
switch (source) {
|
|
case EShSourceGlsl: return new TBuiltIns(); // GLSL builtIns
|
|
#ifdef ENABLE_HLSL
|
|
case EShSourceHlsl: return new TBuiltInParseablesHlsl(); // HLSL intrinsics
|
|
#endif
|
|
|
|
default:
|
|
infoSink.info.message(EPrefixInternalError, "Unable to determine source language");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Create a language specific version of a parse context.
|
|
TParseContextBase* CreateParseContext(TSymbolTable& symbolTable, TIntermediate& intermediate,
|
|
int version, EProfile profile, EShSource source,
|
|
EShLanguage language, TInfoSink& infoSink,
|
|
SpvVersion spvVersion, bool forwardCompatible, EShMessages messages,
|
|
bool parsingBuiltIns, std::string sourceEntryPointName = "")
|
|
{
|
|
switch (source) {
|
|
case EShSourceGlsl: {
|
|
if (sourceEntryPointName.size() == 0)
|
|
intermediate.setEntryPointName("main");
|
|
TString entryPoint = sourceEntryPointName.c_str();
|
|
return new TParseContext(symbolTable, intermediate, parsingBuiltIns, version, profile, spvVersion,
|
|
language, infoSink, forwardCompatible, messages, &entryPoint);
|
|
}
|
|
#ifdef ENABLE_HLSL
|
|
case EShSourceHlsl:
|
|
return new HlslParseContext(symbolTable, intermediate, parsingBuiltIns, version, profile, spvVersion,
|
|
language, infoSink, sourceEntryPointName.c_str(), forwardCompatible, messages);
|
|
#endif
|
|
default:
|
|
infoSink.info.message(EPrefixInternalError, "Unable to determine source language");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
// Local mapping functions for making arrays of symbol tables....
|
|
|
|
const int VersionCount = 17; // index range in MapVersionToIndex
|
|
|
|
int MapVersionToIndex(int version)
|
|
{
|
|
int index = 0;
|
|
|
|
switch (version) {
|
|
case 100: index = 0; break;
|
|
case 110: index = 1; break;
|
|
case 120: index = 2; break;
|
|
case 130: index = 3; break;
|
|
case 140: index = 4; break;
|
|
case 150: index = 5; break;
|
|
case 300: index = 6; break;
|
|
case 330: index = 7; break;
|
|
case 400: index = 8; break;
|
|
case 410: index = 9; break;
|
|
case 420: index = 10; break;
|
|
case 430: index = 11; break;
|
|
case 440: index = 12; break;
|
|
case 310: index = 13; break;
|
|
case 450: index = 14; break;
|
|
case 500: index = 0; break; // HLSL
|
|
case 320: index = 15; break;
|
|
case 460: index = 16; break;
|
|
default: assert(0); break;
|
|
}
|
|
|
|
assert(index < VersionCount);
|
|
|
|
return index;
|
|
}
|
|
|
|
const int SpvVersionCount = 4; // index range in MapSpvVersionToIndex
|
|
|
|
int MapSpvVersionToIndex(const SpvVersion& spvVersion)
|
|
{
|
|
int index = 0;
|
|
|
|
if (spvVersion.openGl > 0)
|
|
index = 1;
|
|
else if (spvVersion.vulkan > 0) {
|
|
if (!spvVersion.vulkanRelaxed)
|
|
index = 2;
|
|
else
|
|
index = 3;
|
|
}
|
|
|
|
assert(index < SpvVersionCount);
|
|
|
|
return index;
|
|
}
|
|
|
|
const int ProfileCount = 4; // index range in MapProfileToIndex
|
|
|
|
int MapProfileToIndex(EProfile profile)
|
|
{
|
|
int index = 0;
|
|
|
|
switch (profile) {
|
|
case ENoProfile: index = 0; break;
|
|
case ECoreProfile: index = 1; break;
|
|
case ECompatibilityProfile: index = 2; break;
|
|
case EEsProfile: index = 3; break;
|
|
default: break;
|
|
}
|
|
|
|
assert(index < ProfileCount);
|
|
|
|
return index;
|
|
}
|
|
|
|
const int SourceCount = 2;
|
|
|
|
int MapSourceToIndex(EShSource source)
|
|
{
|
|
int index = 0;
|
|
|
|
switch (source) {
|
|
case EShSourceGlsl: index = 0; break;
|
|
case EShSourceHlsl: index = 1; break;
|
|
default: break;
|
|
}
|
|
|
|
assert(index < SourceCount);
|
|
|
|
return index;
|
|
}
|
|
|
|
// only one of these needed for non-ES; ES needs 2 for different precision defaults of built-ins
|
|
enum EPrecisionClass {
|
|
EPcGeneral,
|
|
EPcFragment,
|
|
EPcCount
|
|
};
|
|
|
|
// A process-global symbol table per version per profile for built-ins common
|
|
// to multiple stages (languages), and a process-global symbol table per version
|
|
// per profile per stage for built-ins unique to each stage. They will be sparsely
|
|
// populated, so they will only be generated as needed.
|
|
//
|
|
// Each has a different set of built-ins, and we want to preserve that from
|
|
// compile to compile.
|
|
//
|
|
TSymbolTable* CommonSymbolTable[VersionCount][SpvVersionCount][ProfileCount][SourceCount][EPcCount] = {};
|
|
TSymbolTable* SharedSymbolTables[VersionCount][SpvVersionCount][ProfileCount][SourceCount][EShLangCount] = {};
|
|
|
|
TPoolAllocator* PerProcessGPA = nullptr;
|
|
|
|
//
|
|
// Parse and add to the given symbol table the content of the given shader string.
|
|
//
|
|
bool InitializeSymbolTable(const TString& builtIns, int version, EProfile profile, const SpvVersion& spvVersion, EShLanguage language,
|
|
EShSource source, TInfoSink& infoSink, TSymbolTable& symbolTable)
|
|
{
|
|
TIntermediate intermediate(language, version, profile);
|
|
|
|
intermediate.setSource(source);
|
|
|
|
std::unique_ptr<TParseContextBase> parseContext(CreateParseContext(symbolTable, intermediate, version, profile, source,
|
|
language, infoSink, spvVersion, true, EShMsgDefault,
|
|
true));
|
|
|
|
TShader::ForbidIncluder includer;
|
|
TPpContext ppContext(*parseContext, "", includer);
|
|
TScanContext scanContext(*parseContext);
|
|
parseContext->setScanContext(&scanContext);
|
|
parseContext->setPpContext(&ppContext);
|
|
|
|
//
|
|
// Push the symbol table to give it an initial scope. This
|
|
// push should not have a corresponding pop, so that built-ins
|
|
// are preserved, and the test for an empty table fails.
|
|
//
|
|
|
|
symbolTable.push();
|
|
|
|
const char* builtInShaders[2];
|
|
size_t builtInLengths[2];
|
|
builtInShaders[0] = builtIns.c_str();
|
|
builtInLengths[0] = builtIns.size();
|
|
|
|
if (builtInLengths[0] == 0)
|
|
return true;
|
|
|
|
TInputScanner input(1, builtInShaders, builtInLengths);
|
|
if (! parseContext->parseShaderStrings(ppContext, input) != 0) {
|
|
infoSink.info.message(EPrefixInternalError, "Unable to parse built-ins");
|
|
printf("Unable to parse built-ins\n%s\n", infoSink.info.c_str());
|
|
printf("%s\n", builtInShaders[0]);
|
|
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int CommonIndex(EProfile profile, EShLanguage language)
|
|
{
|
|
return (profile == EEsProfile && language == EShLangFragment) ? EPcFragment : EPcGeneral;
|
|
}
|
|
|
|
//
|
|
// To initialize per-stage shared tables, with the common table already complete.
|
|
//
|
|
void InitializeStageSymbolTable(TBuiltInParseables& builtInParseables, int version, EProfile profile, const SpvVersion& spvVersion,
|
|
EShLanguage language, EShSource source, TInfoSink& infoSink, TSymbolTable** commonTable,
|
|
TSymbolTable** symbolTables)
|
|
{
|
|
#ifdef GLSLANG_WEB
|
|
profile = EEsProfile;
|
|
version = 310;
|
|
#elif defined(GLSLANG_ANGLE)
|
|
profile = ECoreProfile;
|
|
version = 450;
|
|
#endif
|
|
|
|
(*symbolTables[language]).adoptLevels(*commonTable[CommonIndex(profile, language)]);
|
|
InitializeSymbolTable(builtInParseables.getStageString(language), version, profile, spvVersion, language, source,
|
|
infoSink, *symbolTables[language]);
|
|
builtInParseables.identifyBuiltIns(version, profile, spvVersion, language, *symbolTables[language]);
|
|
if (profile == EEsProfile && version >= 300)
|
|
(*symbolTables[language]).setNoBuiltInRedeclarations();
|
|
if (version == 110)
|
|
(*symbolTables[language]).setSeparateNameSpaces();
|
|
}
|
|
|
|
//
|
|
// Initialize the full set of shareable symbol tables;
|
|
// The common (cross-stage) and those shareable per-stage.
|
|
//
|
|
bool InitializeSymbolTables(TInfoSink& infoSink, TSymbolTable** commonTable, TSymbolTable** symbolTables, int version, EProfile profile, const SpvVersion& spvVersion, EShSource source)
|
|
{
|
|
#ifdef GLSLANG_WEB
|
|
profile = EEsProfile;
|
|
version = 310;
|
|
#elif defined(GLSLANG_ANGLE)
|
|
profile = ECoreProfile;
|
|
version = 450;
|
|
#endif
|
|
|
|
std::unique_ptr<TBuiltInParseables> builtInParseables(CreateBuiltInParseables(infoSink, source));
|
|
|
|
if (builtInParseables == nullptr)
|
|
return false;
|
|
|
|
builtInParseables->initialize(version, profile, spvVersion);
|
|
|
|
// do the common tables
|
|
InitializeSymbolTable(builtInParseables->getCommonString(), version, profile, spvVersion, EShLangVertex, source,
|
|
infoSink, *commonTable[EPcGeneral]);
|
|
if (profile == EEsProfile)
|
|
InitializeSymbolTable(builtInParseables->getCommonString(), version, profile, spvVersion, EShLangFragment, source,
|
|
infoSink, *commonTable[EPcFragment]);
|
|
|
|
// do the per-stage tables
|
|
|
|
// always have vertex and fragment
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangVertex, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangFragment, source,
|
|
infoSink, commonTable, symbolTables);
|
|
|
|
#ifndef GLSLANG_WEB
|
|
// check for tessellation
|
|
if ((profile != EEsProfile && version >= 150) ||
|
|
(profile == EEsProfile && version >= 310)) {
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangTessControl, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangTessEvaluation, source,
|
|
infoSink, commonTable, symbolTables);
|
|
}
|
|
|
|
// check for geometry
|
|
if ((profile != EEsProfile && version >= 150) ||
|
|
(profile == EEsProfile && version >= 310))
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangGeometry, source,
|
|
infoSink, commonTable, symbolTables);
|
|
|
|
// check for compute
|
|
if ((profile != EEsProfile && version >= 420) ||
|
|
(profile == EEsProfile && version >= 310))
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangCompute, source,
|
|
infoSink, commonTable, symbolTables);
|
|
|
|
#ifndef GLSLANG_ANGLE
|
|
// check for ray tracing stages
|
|
if (profile != EEsProfile && version >= 450) {
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangRayGen, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangIntersect, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangAnyHit, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangClosestHit, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangMiss, source,
|
|
infoSink, commonTable, symbolTables);
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangCallable, source,
|
|
infoSink, commonTable, symbolTables);
|
|
}
|
|
|
|
// check for mesh
|
|
if ((profile != EEsProfile && version >= 450) ||
|
|
(profile == EEsProfile && version >= 320))
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangMeshNV, source,
|
|
infoSink, commonTable, symbolTables);
|
|
|
|
// check for task
|
|
if ((profile != EEsProfile && version >= 450) ||
|
|
(profile == EEsProfile && version >= 320))
|
|
InitializeStageSymbolTable(*builtInParseables, version, profile, spvVersion, EShLangTaskNV, source,
|
|
infoSink, commonTable, symbolTables);
|
|
#endif // !GLSLANG_ANGLE
|
|
#endif // !GLSLANG_WEB
|
|
|
|
return true;
|
|
}
|
|
|
|
bool AddContextSpecificSymbols(const TBuiltInResource* resources, TInfoSink& infoSink, TSymbolTable& symbolTable, int version,
|
|
EProfile profile, const SpvVersion& spvVersion, EShLanguage language, EShSource source)
|
|
{
|
|
std::unique_ptr<TBuiltInParseables> builtInParseables(CreateBuiltInParseables(infoSink, source));
|
|
|
|
if (builtInParseables == nullptr)
|
|
return false;
|
|
|
|
builtInParseables->initialize(*resources, version, profile, spvVersion, language);
|
|
InitializeSymbolTable(builtInParseables->getCommonString(), version, profile, spvVersion, language, source, infoSink, symbolTable);
|
|
builtInParseables->identifyBuiltIns(version, profile, spvVersion, language, symbolTable, *resources);
|
|
|
|
return true;
|
|
}
|
|
|
|
//
|
|
// To do this on the fly, we want to leave the current state of our thread's
|
|
// pool allocator intact, so:
|
|
// - Switch to a new pool for parsing the built-ins
|
|
// - Do the parsing, which builds the symbol table, using the new pool
|
|
// - Switch to the process-global pool to save a copy of the resulting symbol table
|
|
// - Free up the new pool used to parse the built-ins
|
|
// - Switch back to the original thread's pool
|
|
//
|
|
// This only gets done the first time any thread needs a particular symbol table
|
|
// (lazy evaluation).
|
|
//
|
|
void SetupBuiltinSymbolTable(int version, EProfile profile, const SpvVersion& spvVersion, EShSource source)
|
|
{
|
|
TInfoSink infoSink;
|
|
|
|
// Make sure only one thread tries to do this at a time
|
|
glslang::GetGlobalLock();
|
|
|
|
// See if it's already been done for this version/profile combination
|
|
int versionIndex = MapVersionToIndex(version);
|
|
int spvVersionIndex = MapSpvVersionToIndex(spvVersion);
|
|
int profileIndex = MapProfileToIndex(profile);
|
|
int sourceIndex = MapSourceToIndex(source);
|
|
if (CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][EPcGeneral]) {
|
|
glslang::ReleaseGlobalLock();
|
|
|
|
return;
|
|
}
|
|
|
|
// Switch to a new pool
|
|
TPoolAllocator& previousAllocator = GetThreadPoolAllocator();
|
|
TPoolAllocator* builtInPoolAllocator = new TPoolAllocator;
|
|
SetThreadPoolAllocator(builtInPoolAllocator);
|
|
|
|
// Dynamically allocate the local symbol tables so we can control when they are deallocated WRT when the pool is popped.
|
|
TSymbolTable* commonTable[EPcCount];
|
|
TSymbolTable* stageTables[EShLangCount];
|
|
for (int precClass = 0; precClass < EPcCount; ++precClass)
|
|
commonTable[precClass] = new TSymbolTable;
|
|
for (int stage = 0; stage < EShLangCount; ++stage)
|
|
stageTables[stage] = new TSymbolTable;
|
|
|
|
// Generate the local symbol tables using the new pool
|
|
InitializeSymbolTables(infoSink, commonTable, stageTables, version, profile, spvVersion, source);
|
|
|
|
// Switch to the process-global pool
|
|
SetThreadPoolAllocator(PerProcessGPA);
|
|
|
|
// Copy the local symbol tables from the new pool to the global tables using the process-global pool
|
|
for (int precClass = 0; precClass < EPcCount; ++precClass) {
|
|
if (! commonTable[precClass]->isEmpty()) {
|
|
CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][precClass] = new TSymbolTable;
|
|
CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][precClass]->copyTable(*commonTable[precClass]);
|
|
CommonSymbolTable[versionIndex][spvVersionIndex][profileIndex][sourceIndex][precClass]->readOnly();
|
|
}
|
|
}
|
|
for (int stage = 0; stage < EShLangCount; ++stage) {
|
|
if (! stageTables[stage]->isEmpty()) {
|
|
SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage] = new TSymbolTable;
|
|
SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage]->adoptLevels(*CommonSymbolTable
|
|
[versionIndex][spvVersionIndex][profileIndex][sourceIndex][CommonIndex(profile, (EShLanguage)stage)]);
|
|
SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage]->copyTable(*stageTables[stage]);
|
|
SharedSymbolTables[versionIndex][spvVersionIndex][profileIndex][sourceIndex][stage]->readOnly();
|
|
}
|
|
}
|
|
|
|
// Clean up the local tables before deleting the pool they used.
|
|
for (int precClass = 0; precClass < EPcCount; ++precClass)
|
|
delete commonTable[precClass];
|
|
for (int stage = 0; stage < EShLangCount; ++stage)
|
|
delete stageTables[stage];
|
|
|
|
delete builtInPoolAllocator;
|
|
SetThreadPoolAllocator(&previousAllocator);
|
|
|
|
glslang::ReleaseGlobalLock();
|
|
}
|
|
|
|
// Function to Print all builtins
|
|
void DumpBuiltinSymbolTable(TInfoSink& infoSink, const TSymbolTable& symbolTable)
|
|
{
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
infoSink.debug << "BuiltinSymbolTable {\n";
|
|
|
|
symbolTable.dump(infoSink, true);
|
|
|
|
infoSink.debug << "}\n";
|
|
#endif
|
|
}
|
|
|
|
// Return true if the shader was correctly specified for version/profile/stage.
|
|
bool DeduceVersionProfile(TInfoSink& infoSink, EShLanguage stage, bool versionNotFirst, int defaultVersion,
|
|
EShSource source, int& version, EProfile& profile, const SpvVersion& spvVersion)
|
|
{
|
|
const int FirstProfileVersion = 150;
|
|
bool correct = true;
|
|
|
|
if (source == EShSourceHlsl) {
|
|
version = 500; // shader model; currently a characteristic of glslang, not the input
|
|
profile = ECoreProfile; // allow doubles in prototype parsing
|
|
return correct;
|
|
}
|
|
|
|
// Get a version...
|
|
if (version == 0) {
|
|
version = defaultVersion;
|
|
// infoSink.info.message(EPrefixWarning, "#version: statement missing; use #version on first line of shader");
|
|
}
|
|
|
|
// Get a good profile...
|
|
if (profile == ENoProfile) {
|
|
if (version == 300 || version == 310 || version == 320) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: versions 300, 310, and 320 require specifying the 'es' profile");
|
|
profile = EEsProfile;
|
|
} else if (version == 100)
|
|
profile = EEsProfile;
|
|
else if (version >= FirstProfileVersion)
|
|
profile = ECoreProfile;
|
|
else
|
|
profile = ENoProfile;
|
|
} else {
|
|
// a profile was provided...
|
|
if (version < 150) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: versions before 150 do not allow a profile token");
|
|
if (version == 100)
|
|
profile = EEsProfile;
|
|
else
|
|
profile = ENoProfile;
|
|
} else if (version == 300 || version == 310 || version == 320) {
|
|
if (profile != EEsProfile) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: versions 300, 310, and 320 support only the es profile");
|
|
}
|
|
profile = EEsProfile;
|
|
} else {
|
|
if (profile == EEsProfile) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: only version 300, 310, and 320 support the es profile");
|
|
if (version >= FirstProfileVersion)
|
|
profile = ECoreProfile;
|
|
else
|
|
profile = ENoProfile;
|
|
}
|
|
// else: typical desktop case... e.g., "#version 410 core"
|
|
}
|
|
}
|
|
|
|
// Fix version...
|
|
switch (version) {
|
|
// ES versions
|
|
case 100: break;
|
|
case 300: break;
|
|
case 310: break;
|
|
case 320: break;
|
|
|
|
// desktop versions
|
|
case 110: break;
|
|
case 120: break;
|
|
case 130: break;
|
|
case 140: break;
|
|
case 150: break;
|
|
case 330: break;
|
|
case 400: break;
|
|
case 410: break;
|
|
case 420: break;
|
|
case 430: break;
|
|
case 440: break;
|
|
case 450: break;
|
|
case 460: break;
|
|
|
|
// unknown version
|
|
default:
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "version not supported");
|
|
if (profile == EEsProfile)
|
|
version = 310;
|
|
else {
|
|
version = 450;
|
|
profile = ECoreProfile;
|
|
}
|
|
break;
|
|
}
|
|
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
// Correct for stage type...
|
|
switch (stage) {
|
|
case EShLangGeometry:
|
|
if ((profile == EEsProfile && version < 310) ||
|
|
(profile != EEsProfile && version < 150)) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: geometry shaders require es profile with version 310 or non-es profile with version 150 or above");
|
|
version = (profile == EEsProfile) ? 310 : 150;
|
|
if (profile == EEsProfile || profile == ENoProfile)
|
|
profile = ECoreProfile;
|
|
}
|
|
break;
|
|
case EShLangTessControl:
|
|
case EShLangTessEvaluation:
|
|
if ((profile == EEsProfile && version < 310) ||
|
|
(profile != EEsProfile && version < 150)) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: tessellation shaders require es profile with version 310 or non-es profile with version 150 or above");
|
|
version = (profile == EEsProfile) ? 310 : 400; // 150 supports the extension, correction is to 400 which does not
|
|
if (profile == EEsProfile || profile == ENoProfile)
|
|
profile = ECoreProfile;
|
|
}
|
|
break;
|
|
case EShLangCompute:
|
|
if ((profile == EEsProfile && version < 310) ||
|
|
(profile != EEsProfile && version < 420)) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: compute shaders require es profile with version 310 or above, or non-es profile with version 420 or above");
|
|
version = profile == EEsProfile ? 310 : 420;
|
|
}
|
|
break;
|
|
case EShLangRayGen:
|
|
case EShLangIntersect:
|
|
case EShLangAnyHit:
|
|
case EShLangClosestHit:
|
|
case EShLangMiss:
|
|
case EShLangCallable:
|
|
if (profile == EEsProfile || version < 460) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: ray tracing shaders require non-es profile with version 460 or above");
|
|
version = 460;
|
|
}
|
|
break;
|
|
case EShLangMeshNV:
|
|
case EShLangTaskNV:
|
|
if ((profile == EEsProfile && version < 320) ||
|
|
(profile != EEsProfile && version < 450)) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: mesh/task shaders require es profile with version 320 or above, or non-es profile with version 450 or above");
|
|
version = profile == EEsProfile ? 320 : 450;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (profile == EEsProfile && version >= 300 && versionNotFirst) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: statement must appear first in es-profile shader; before comments or newlines");
|
|
}
|
|
|
|
// Check for SPIR-V compatibility
|
|
if (spvVersion.spv != 0) {
|
|
switch (profile) {
|
|
case EEsProfile:
|
|
if (version < 310) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: ES shaders for SPIR-V require version 310 or higher");
|
|
version = 310;
|
|
}
|
|
break;
|
|
case ECompatibilityProfile:
|
|
infoSink.info.message(EPrefixError, "#version: compilation for SPIR-V does not support the compatibility profile");
|
|
break;
|
|
default:
|
|
if (spvVersion.vulkan > 0 && version < 140) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: Desktop shaders for Vulkan SPIR-V require version 140 or higher");
|
|
version = 140;
|
|
}
|
|
if (spvVersion.openGl >= 100 && version < 330) {
|
|
correct = false;
|
|
infoSink.info.message(EPrefixError, "#version: Desktop shaders for OpenGL SPIR-V require version 330 or higher");
|
|
version = 330;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return correct;
|
|
}
|
|
|
|
// There are multiple paths in for setting environment stuff.
|
|
// TEnvironment takes precedence, for what it sets, so sort all this out.
|
|
// Ideally, the internal code could be made to use TEnvironment, but for
|
|
// now, translate it to the historically used parameters.
|
|
void TranslateEnvironment(const TEnvironment* environment, EShMessages& messages, EShSource& source,
|
|
EShLanguage& stage, SpvVersion& spvVersion)
|
|
{
|
|
// Set up environmental defaults, first ignoring 'environment'.
|
|
if (messages & EShMsgSpvRules)
|
|
spvVersion.spv = EShTargetSpv_1_0;
|
|
if (messages & EShMsgVulkanRules) {
|
|
spvVersion.vulkan = EShTargetVulkan_1_0;
|
|
spvVersion.vulkanGlsl = 100;
|
|
} else if (spvVersion.spv != 0)
|
|
spvVersion.openGl = 100;
|
|
|
|
// Now, override, based on any content set in 'environment'.
|
|
// 'environment' must be cleared to ESh*None settings when items
|
|
// are not being set.
|
|
if (environment != nullptr) {
|
|
// input language
|
|
if (environment->input.languageFamily != EShSourceNone) {
|
|
stage = environment->input.stage;
|
|
switch (environment->input.dialect) {
|
|
case EShClientNone:
|
|
break;
|
|
case EShClientVulkan:
|
|
spvVersion.vulkanGlsl = environment->input.dialectVersion;
|
|
spvVersion.vulkanRelaxed = environment->input.vulkanRulesRelaxed;
|
|
break;
|
|
case EShClientOpenGL:
|
|
spvVersion.openGl = environment->input.dialectVersion;
|
|
break;
|
|
case EShClientCount:
|
|
assert(0);
|
|
break;
|
|
}
|
|
switch (environment->input.languageFamily) {
|
|
case EShSourceNone:
|
|
break;
|
|
case EShSourceGlsl:
|
|
source = EShSourceGlsl;
|
|
messages = static_cast<EShMessages>(messages & ~EShMsgReadHlsl);
|
|
break;
|
|
case EShSourceHlsl:
|
|
source = EShSourceHlsl;
|
|
messages = static_cast<EShMessages>(messages | EShMsgReadHlsl);
|
|
break;
|
|
case EShSourceCount:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// client
|
|
switch (environment->client.client) {
|
|
case EShClientVulkan:
|
|
spvVersion.vulkan = environment->client.version;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// generated code
|
|
switch (environment->target.language) {
|
|
case EshTargetSpv:
|
|
spvVersion.spv = environment->target.version;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Most processes are recorded when set in the intermediate representation,
|
|
// These are the few that are not.
|
|
void RecordProcesses(TIntermediate& intermediate, EShMessages messages, const std::string& sourceEntryPointName)
|
|
{
|
|
if ((messages & EShMsgRelaxedErrors) != 0)
|
|
intermediate.addProcess("relaxed-errors");
|
|
if ((messages & EShMsgSuppressWarnings) != 0)
|
|
intermediate.addProcess("suppress-warnings");
|
|
if ((messages & EShMsgKeepUncalled) != 0)
|
|
intermediate.addProcess("keep-uncalled");
|
|
if (sourceEntryPointName.size() > 0) {
|
|
intermediate.addProcess("source-entrypoint");
|
|
intermediate.addProcessArgument(sourceEntryPointName);
|
|
}
|
|
}
|
|
|
|
// This is the common setup and cleanup code for PreprocessDeferred and
|
|
// CompileDeferred.
|
|
// It takes any callable with a signature of
|
|
// bool (TParseContextBase& parseContext, TPpContext& ppContext,
|
|
// TInputScanner& input, bool versionWillBeError,
|
|
// TSymbolTable& , TIntermediate& ,
|
|
// EShOptimizationLevel , EShMessages );
|
|
// Which returns false if a failure was detected and true otherwise.
|
|
//
|
|
template<typename ProcessingContext>
|
|
bool ProcessDeferred(
|
|
TCompiler* compiler,
|
|
const char* const shaderStrings[],
|
|
const int numStrings,
|
|
const int* inputLengths,
|
|
const char* const stringNames[],
|
|
const char* customPreamble,
|
|
const EShOptimizationLevel optLevel,
|
|
const TBuiltInResource* resources,
|
|
int defaultVersion, // use 100 for ES environment, 110 for desktop; this is the GLSL version, not SPIR-V or Vulkan
|
|
EProfile defaultProfile,
|
|
// set version/profile to defaultVersion/defaultProfile regardless of the #version
|
|
// directive in the source code
|
|
bool forceDefaultVersionAndProfile,
|
|
bool forwardCompatible, // give errors for use of deprecated features
|
|
EShMessages messages, // warnings/errors/AST; things to print out
|
|
TIntermediate& intermediate, // returned tree, etc.
|
|
ProcessingContext& processingContext,
|
|
bool requireNonempty,
|
|
TShader::Includer& includer,
|
|
const std::string sourceEntryPointName = "",
|
|
const TEnvironment* environment = nullptr) // optional way of fully setting all versions, overriding the above
|
|
{
|
|
// This must be undone (.pop()) by the caller, after it finishes consuming the created tree.
|
|
GetThreadPoolAllocator().push();
|
|
|
|
if (numStrings == 0)
|
|
return true;
|
|
|
|
// Move to length-based strings, rather than null-terminated strings.
|
|
// Also, add strings to include the preamble and to ensure the shader is not null,
|
|
// which lets the grammar accept what was a null (post preprocessing) shader.
|
|
//
|
|
// Shader will look like
|
|
// string 0: system preamble
|
|
// string 1: custom preamble
|
|
// string 2...numStrings+1: user's shader
|
|
// string numStrings+2: "int;"
|
|
const int numPre = 2;
|
|
const int numPost = requireNonempty? 1 : 0;
|
|
const int numTotal = numPre + numStrings + numPost;
|
|
std::unique_ptr<size_t[]> lengths(new size_t[numTotal]);
|
|
std::unique_ptr<const char*[]> strings(new const char*[numTotal]);
|
|
std::unique_ptr<const char*[]> names(new const char*[numTotal]);
|
|
for (int s = 0; s < numStrings; ++s) {
|
|
strings[s + numPre] = shaderStrings[s];
|
|
if (inputLengths == nullptr || inputLengths[s] < 0)
|
|
lengths[s + numPre] = strlen(shaderStrings[s]);
|
|
else
|
|
lengths[s + numPre] = inputLengths[s];
|
|
}
|
|
if (stringNames != nullptr) {
|
|
for (int s = 0; s < numStrings; ++s)
|
|
names[s + numPre] = stringNames[s];
|
|
} else {
|
|
for (int s = 0; s < numStrings; ++s)
|
|
names[s + numPre] = nullptr;
|
|
}
|
|
|
|
// Get all the stages, languages, clients, and other environment
|
|
// stuff sorted out.
|
|
EShSource sourceGuess = (messages & EShMsgReadHlsl) != 0 ? EShSourceHlsl : EShSourceGlsl;
|
|
SpvVersion spvVersion;
|
|
EShLanguage stage = compiler->getLanguage();
|
|
TranslateEnvironment(environment, messages, sourceGuess, stage, spvVersion);
|
|
#ifdef ENABLE_HLSL
|
|
EShSource source = sourceGuess;
|
|
if (environment != nullptr && environment->target.hlslFunctionality1)
|
|
intermediate.setHlslFunctionality1();
|
|
#else
|
|
const EShSource source = EShSourceGlsl;
|
|
#endif
|
|
// First, without using the preprocessor or parser, find the #version, so we know what
|
|
// symbol tables, processing rules, etc. to set up. This does not need the extra strings
|
|
// outlined above, just the user shader, after the system and user preambles.
|
|
glslang::TInputScanner userInput(numStrings, &strings[numPre], &lengths[numPre]);
|
|
int version = 0;
|
|
EProfile profile = ENoProfile;
|
|
bool versionNotFirstToken = false;
|
|
bool versionNotFirst = (source == EShSourceHlsl)
|
|
? true
|
|
: userInput.scanVersion(version, profile, versionNotFirstToken);
|
|
bool versionNotFound = version == 0;
|
|
if (forceDefaultVersionAndProfile && source == EShSourceGlsl) {
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
if (! (messages & EShMsgSuppressWarnings) && ! versionNotFound &&
|
|
(version != defaultVersion || profile != defaultProfile)) {
|
|
compiler->infoSink.info << "Warning, (version, profile) forced to be ("
|
|
<< defaultVersion << ", " << ProfileName(defaultProfile)
|
|
<< "), while in source code it is ("
|
|
<< version << ", " << ProfileName(profile) << ")\n";
|
|
}
|
|
#endif
|
|
if (versionNotFound) {
|
|
versionNotFirstToken = false;
|
|
versionNotFirst = false;
|
|
versionNotFound = false;
|
|
}
|
|
version = defaultVersion;
|
|
profile = defaultProfile;
|
|
}
|
|
|
|
bool goodVersion = DeduceVersionProfile(compiler->infoSink, stage,
|
|
versionNotFirst, defaultVersion, source, version, profile, spvVersion);
|
|
#ifdef GLSLANG_WEB
|
|
profile = EEsProfile;
|
|
version = 310;
|
|
#elif defined(GLSLANG_ANGLE)
|
|
profile = ECoreProfile;
|
|
version = 450;
|
|
#endif
|
|
|
|
bool versionWillBeError = (versionNotFound || (profile == EEsProfile && version >= 300 && versionNotFirst));
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
bool warnVersionNotFirst = false;
|
|
if (! versionWillBeError && versionNotFirstToken) {
|
|
if (messages & EShMsgRelaxedErrors)
|
|
warnVersionNotFirst = true;
|
|
else
|
|
versionWillBeError = true;
|
|
}
|
|
#endif
|
|
|
|
intermediate.setSource(source);
|
|
intermediate.setVersion(version);
|
|
intermediate.setProfile(profile);
|
|
intermediate.setSpv(spvVersion);
|
|
RecordProcesses(intermediate, messages, sourceEntryPointName);
|
|
if (spvVersion.vulkan > 0)
|
|
intermediate.setOriginUpperLeft();
|
|
#ifdef ENABLE_HLSL
|
|
if ((messages & EShMsgHlslOffsets) || source == EShSourceHlsl)
|
|
intermediate.setHlslOffsets();
|
|
#endif
|
|
if (messages & EShMsgDebugInfo) {
|
|
intermediate.setSourceFile(names[numPre]);
|
|
for (int s = 0; s < numStrings; ++s) {
|
|
// The string may not be null-terminated, so make sure we provide
|
|
// the length along with the string.
|
|
intermediate.addSourceText(strings[numPre + s], lengths[numPre + s]);
|
|
}
|
|
}
|
|
SetupBuiltinSymbolTable(version, profile, spvVersion, source);
|
|
|
|
TSymbolTable* cachedTable = SharedSymbolTables[MapVersionToIndex(version)]
|
|
[MapSpvVersionToIndex(spvVersion)]
|
|
[MapProfileToIndex(profile)]
|
|
[MapSourceToIndex(source)]
|
|
[stage];
|
|
|
|
// Dynamically allocate the symbol table so we can control when it is deallocated WRT the pool.
|
|
std::unique_ptr<TSymbolTable> symbolTable(new TSymbolTable);
|
|
if (cachedTable)
|
|
symbolTable->adoptLevels(*cachedTable);
|
|
|
|
if (intermediate.getUniqueId() != 0)
|
|
symbolTable->overwriteUniqueId(intermediate.getUniqueId());
|
|
|
|
// Add built-in symbols that are potentially context dependent;
|
|
// they get popped again further down.
|
|
if (! AddContextSpecificSymbols(resources, compiler->infoSink, *symbolTable, version, profile, spvVersion,
|
|
stage, source)) {
|
|
return false;
|
|
}
|
|
|
|
if (messages & EShMsgBuiltinSymbolTable)
|
|
DumpBuiltinSymbolTable(compiler->infoSink, *symbolTable);
|
|
|
|
//
|
|
// Now we can process the full shader under proper symbols and rules.
|
|
//
|
|
|
|
std::unique_ptr<TParseContextBase> parseContext(CreateParseContext(*symbolTable, intermediate, version, profile, source,
|
|
stage, compiler->infoSink,
|
|
spvVersion, forwardCompatible, messages, false, sourceEntryPointName));
|
|
TPpContext ppContext(*parseContext, names[numPre] ? names[numPre] : "", includer);
|
|
|
|
// only GLSL (bison triggered, really) needs an externally set scan context
|
|
glslang::TScanContext scanContext(*parseContext);
|
|
if (source == EShSourceGlsl)
|
|
parseContext->setScanContext(&scanContext);
|
|
|
|
parseContext->setPpContext(&ppContext);
|
|
parseContext->setLimits(*resources);
|
|
if (! goodVersion)
|
|
parseContext->addError();
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
if (warnVersionNotFirst) {
|
|
TSourceLoc loc;
|
|
loc.init();
|
|
parseContext->warn(loc, "Illegal to have non-comment, non-whitespace tokens before #version", "#version", "");
|
|
}
|
|
#endif
|
|
|
|
parseContext->initializeExtensionBehavior();
|
|
|
|
// Fill in the strings as outlined above.
|
|
std::string preamble;
|
|
parseContext->getPreamble(preamble);
|
|
strings[0] = preamble.c_str();
|
|
lengths[0] = strlen(strings[0]);
|
|
names[0] = nullptr;
|
|
strings[1] = customPreamble;
|
|
lengths[1] = strlen(strings[1]);
|
|
names[1] = nullptr;
|
|
assert(2 == numPre);
|
|
if (requireNonempty) {
|
|
const int postIndex = numStrings + numPre;
|
|
strings[postIndex] = "\n int;";
|
|
lengths[postIndex] = strlen(strings[numStrings + numPre]);
|
|
names[postIndex] = nullptr;
|
|
}
|
|
TInputScanner fullInput(numStrings + numPre + numPost, strings.get(), lengths.get(), names.get(), numPre, numPost);
|
|
|
|
// Push a new symbol allocation scope that will get used for the shader's globals.
|
|
symbolTable->push();
|
|
|
|
bool success = processingContext(*parseContext, ppContext, fullInput,
|
|
versionWillBeError, *symbolTable,
|
|
intermediate, optLevel, messages);
|
|
intermediate.setUniqueId(symbolTable->getMaxSymbolId());
|
|
return success;
|
|
}
|
|
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
|
|
// Responsible for keeping track of the most recent source string and line in
|
|
// the preprocessor and outputting newlines appropriately if the source string
|
|
// or line changes.
|
|
class SourceLineSynchronizer {
|
|
public:
|
|
SourceLineSynchronizer(const std::function<int()>& lastSourceIndex,
|
|
std::string* output)
|
|
: getLastSourceIndex(lastSourceIndex), output(output), lastSource(-1), lastLine(0) {}
|
|
// SourceLineSynchronizer(const SourceLineSynchronizer&) = delete;
|
|
// SourceLineSynchronizer& operator=(const SourceLineSynchronizer&) = delete;
|
|
|
|
// Sets the internally tracked source string index to that of the most
|
|
// recently read token. If we switched to a new source string, returns
|
|
// true and inserts a newline. Otherwise, returns false and outputs nothing.
|
|
bool syncToMostRecentString() {
|
|
if (getLastSourceIndex() != lastSource) {
|
|
// After switching to a new source string, we need to reset lastLine
|
|
// because line number resets every time a new source string is
|
|
// used. We also need to output a newline to separate the output
|
|
// from the previous source string (if there is one).
|
|
if (lastSource != -1 || lastLine != 0)
|
|
*output += '\n';
|
|
lastSource = getLastSourceIndex();
|
|
lastLine = -1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Calls syncToMostRecentString() and then sets the internally tracked line
|
|
// number to tokenLine. If we switched to a new line, returns true and inserts
|
|
// newlines appropriately. Otherwise, returns false and outputs nothing.
|
|
bool syncToLine(int tokenLine) {
|
|
syncToMostRecentString();
|
|
const bool newLineStarted = lastLine < tokenLine;
|
|
for (; lastLine < tokenLine; ++lastLine) {
|
|
if (lastLine > 0) *output += '\n';
|
|
}
|
|
return newLineStarted;
|
|
}
|
|
|
|
// Sets the internally tracked line number to newLineNum.
|
|
void setLineNum(int newLineNum) { lastLine = newLineNum; }
|
|
|
|
private:
|
|
SourceLineSynchronizer& operator=(const SourceLineSynchronizer&);
|
|
|
|
// A function for getting the index of the last valid source string we've
|
|
// read tokens from.
|
|
const std::function<int()> getLastSourceIndex;
|
|
// output string for newlines.
|
|
std::string* output;
|
|
// lastSource is the source string index (starting from 0) of the last token
|
|
// processed. It is tracked in order for newlines to be inserted when a new
|
|
// source string starts. -1 means we haven't started processing any source
|
|
// string.
|
|
int lastSource;
|
|
// lastLine is the line number (starting from 1) of the last token processed.
|
|
// It is tracked in order for newlines to be inserted when a token appears
|
|
// on a new line. 0 means we haven't started processing any line in the
|
|
// current source string.
|
|
int lastLine;
|
|
};
|
|
|
|
// DoPreprocessing is a valid ProcessingContext template argument,
|
|
// which only performs the preprocessing step of compilation.
|
|
// It places the result in the "string" argument to its constructor.
|
|
//
|
|
// This is not an officially supported or fully working path.
|
|
struct DoPreprocessing {
|
|
explicit DoPreprocessing(std::string* string): outputString(string) {}
|
|
bool operator()(TParseContextBase& parseContext, TPpContext& ppContext,
|
|
TInputScanner& input, bool versionWillBeError,
|
|
TSymbolTable&, TIntermediate&,
|
|
EShOptimizationLevel, EShMessages)
|
|
{
|
|
// This is a list of tokens that do not require a space before or after.
|
|
static const std::string unNeededSpaceTokens = ";()[]";
|
|
static const std::string noSpaceBeforeTokens = ",";
|
|
glslang::TPpToken ppToken;
|
|
|
|
parseContext.setScanner(&input);
|
|
ppContext.setInput(input, versionWillBeError);
|
|
|
|
std::string outputBuffer;
|
|
SourceLineSynchronizer lineSync(
|
|
std::bind(&TInputScanner::getLastValidSourceIndex, &input), &outputBuffer);
|
|
|
|
parseContext.setExtensionCallback([&lineSync, &outputBuffer](
|
|
int line, const char* extension, const char* behavior) {
|
|
lineSync.syncToLine(line);
|
|
outputBuffer += "#extension ";
|
|
outputBuffer += extension;
|
|
outputBuffer += " : ";
|
|
outputBuffer += behavior;
|
|
});
|
|
|
|
parseContext.setLineCallback([&lineSync, &outputBuffer, &parseContext](
|
|
int curLineNum, int newLineNum, bool hasSource, int sourceNum, const char* sourceName) {
|
|
// SourceNum is the number of the source-string that is being parsed.
|
|
lineSync.syncToLine(curLineNum);
|
|
outputBuffer += "#line ";
|
|
outputBuffer += std::to_string(newLineNum);
|
|
if (hasSource) {
|
|
outputBuffer += ' ';
|
|
if (sourceName != nullptr) {
|
|
outputBuffer += '\"';
|
|
outputBuffer += sourceName;
|
|
outputBuffer += '\"';
|
|
} else {
|
|
outputBuffer += std::to_string(sourceNum);
|
|
}
|
|
}
|
|
if (parseContext.lineDirectiveShouldSetNextLine()) {
|
|
// newLineNum is the new line number for the line following the #line
|
|
// directive. So the new line number for the current line is
|
|
newLineNum -= 1;
|
|
}
|
|
outputBuffer += '\n';
|
|
// And we are at the next line of the #line directive now.
|
|
lineSync.setLineNum(newLineNum + 1);
|
|
});
|
|
|
|
parseContext.setVersionCallback(
|
|
[&lineSync, &outputBuffer](int line, int version, const char* str) {
|
|
lineSync.syncToLine(line);
|
|
outputBuffer += "#version ";
|
|
outputBuffer += std::to_string(version);
|
|
if (str) {
|
|
outputBuffer += ' ';
|
|
outputBuffer += str;
|
|
}
|
|
});
|
|
|
|
parseContext.setPragmaCallback([&lineSync, &outputBuffer](
|
|
int line, const glslang::TVector<glslang::TString>& ops) {
|
|
lineSync.syncToLine(line);
|
|
outputBuffer += "#pragma ";
|
|
for(size_t i = 0; i < ops.size(); ++i) {
|
|
outputBuffer += ops[i].c_str();
|
|
}
|
|
});
|
|
|
|
parseContext.setErrorCallback([&lineSync, &outputBuffer](
|
|
int line, const char* errorMessage) {
|
|
lineSync.syncToLine(line);
|
|
outputBuffer += "#error ";
|
|
outputBuffer += errorMessage;
|
|
});
|
|
|
|
int lastToken = EndOfInput; // lastToken records the last token processed.
|
|
do {
|
|
int token = ppContext.tokenize(ppToken);
|
|
if (token == EndOfInput)
|
|
break;
|
|
|
|
bool isNewString = lineSync.syncToMostRecentString();
|
|
bool isNewLine = lineSync.syncToLine(ppToken.loc.line);
|
|
|
|
if (isNewLine) {
|
|
// Don't emit whitespace onto empty lines.
|
|
// Copy any whitespace characters at the start of a line
|
|
// from the input to the output.
|
|
outputBuffer += std::string(ppToken.loc.column - 1, ' ');
|
|
}
|
|
|
|
// Output a space in between tokens, but not at the start of a line,
|
|
// and also not around special tokens. This helps with readability
|
|
// and consistency.
|
|
if (!isNewString && !isNewLine && lastToken != EndOfInput &&
|
|
(unNeededSpaceTokens.find((char)token) == std::string::npos) &&
|
|
(unNeededSpaceTokens.find((char)lastToken) == std::string::npos) &&
|
|
(noSpaceBeforeTokens.find((char)token) == std::string::npos)) {
|
|
outputBuffer += ' ';
|
|
}
|
|
lastToken = token;
|
|
if (token == PpAtomConstString)
|
|
outputBuffer += "\"";
|
|
outputBuffer += ppToken.name;
|
|
if (token == PpAtomConstString)
|
|
outputBuffer += "\"";
|
|
} while (true);
|
|
outputBuffer += '\n';
|
|
*outputString = std::move(outputBuffer);
|
|
|
|
bool success = true;
|
|
if (parseContext.getNumErrors() > 0) {
|
|
success = false;
|
|
parseContext.infoSink.info.prefix(EPrefixError);
|
|
parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors. No code generated.\n\n";
|
|
}
|
|
return success;
|
|
}
|
|
std::string* outputString;
|
|
};
|
|
|
|
#endif
|
|
|
|
// DoFullParse is a valid ProcessingConext template argument for fully
|
|
// parsing the shader. It populates the "intermediate" with the AST.
|
|
struct DoFullParse{
|
|
bool operator()(TParseContextBase& parseContext, TPpContext& ppContext,
|
|
TInputScanner& fullInput, bool versionWillBeError,
|
|
TSymbolTable&, TIntermediate& intermediate,
|
|
EShOptimizationLevel optLevel, EShMessages messages)
|
|
{
|
|
bool success = true;
|
|
// Parse the full shader.
|
|
if (! parseContext.parseShaderStrings(ppContext, fullInput, versionWillBeError))
|
|
success = false;
|
|
|
|
if (success && intermediate.getTreeRoot()) {
|
|
if (optLevel == EShOptNoGeneration)
|
|
parseContext.infoSink.info.message(EPrefixNone, "No errors. No code generation or linking was requested.");
|
|
else
|
|
success = intermediate.postProcess(intermediate.getTreeRoot(), parseContext.getLanguage());
|
|
} else if (! success) {
|
|
parseContext.infoSink.info.prefix(EPrefixError);
|
|
parseContext.infoSink.info << parseContext.getNumErrors() << " compilation errors. No code generated.\n\n";
|
|
}
|
|
|
|
#ifndef GLSLANG_ANGLE
|
|
if (messages & EShMsgAST)
|
|
intermediate.output(parseContext.infoSink, true);
|
|
#endif
|
|
|
|
return success;
|
|
}
|
|
};
|
|
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
// Take a single compilation unit, and run the preprocessor on it.
|
|
// Return: True if there were no issues found in preprocessing,
|
|
// False if during preprocessing any unknown version, pragmas or
|
|
// extensions were found.
|
|
//
|
|
// NOTE: Doing just preprocessing to obtain a correct preprocessed shader string
|
|
// is not an officially supported or fully working path.
|
|
bool PreprocessDeferred(
|
|
TCompiler* compiler,
|
|
const char* const shaderStrings[],
|
|
const int numStrings,
|
|
const int* inputLengths,
|
|
const char* const stringNames[],
|
|
const char* preamble,
|
|
const EShOptimizationLevel optLevel,
|
|
const TBuiltInResource* resources,
|
|
int defaultVersion, // use 100 for ES environment, 110 for desktop
|
|
EProfile defaultProfile,
|
|
bool forceDefaultVersionAndProfile,
|
|
bool forwardCompatible, // give errors for use of deprecated features
|
|
EShMessages messages, // warnings/errors/AST; things to print out
|
|
TShader::Includer& includer,
|
|
TIntermediate& intermediate, // returned tree, etc.
|
|
std::string* outputString,
|
|
TEnvironment* environment = nullptr)
|
|
{
|
|
DoPreprocessing parser(outputString);
|
|
return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, stringNames,
|
|
preamble, optLevel, resources, defaultVersion,
|
|
defaultProfile, forceDefaultVersionAndProfile,
|
|
forwardCompatible, messages, intermediate, parser,
|
|
false, includer, "", environment);
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// do a partial compile on the given strings for a single compilation unit
|
|
// for a potential deferred link into a single stage (and deferred full compile of that
|
|
// stage through machine-dependent compilation).
|
|
//
|
|
// all preprocessing, parsing, semantic checks, etc. for a single compilation unit
|
|
// are done here.
|
|
//
|
|
// return: the tree and other information is filled into the intermediate argument,
|
|
// and true is returned by the function for success.
|
|
//
|
|
bool CompileDeferred(
|
|
TCompiler* compiler,
|
|
const char* const shaderStrings[],
|
|
const int numStrings,
|
|
const int* inputLengths,
|
|
const char* const stringNames[],
|
|
const char* preamble,
|
|
const EShOptimizationLevel optLevel,
|
|
const TBuiltInResource* resources,
|
|
int defaultVersion, // use 100 for ES environment, 110 for desktop
|
|
EProfile defaultProfile,
|
|
bool forceDefaultVersionAndProfile,
|
|
bool forwardCompatible, // give errors for use of deprecated features
|
|
EShMessages messages, // warnings/errors/AST; things to print out
|
|
TIntermediate& intermediate,// returned tree, etc.
|
|
TShader::Includer& includer,
|
|
const std::string sourceEntryPointName = "",
|
|
TEnvironment* environment = nullptr)
|
|
{
|
|
DoFullParse parser;
|
|
return ProcessDeferred(compiler, shaderStrings, numStrings, inputLengths, stringNames,
|
|
preamble, optLevel, resources, defaultVersion,
|
|
defaultProfile, forceDefaultVersionAndProfile,
|
|
forwardCompatible, messages, intermediate, parser,
|
|
true, includer, sourceEntryPointName, environment);
|
|
}
|
|
|
|
} // end anonymous namespace for local functions
|
|
|
|
//
|
|
// ShInitialize() should be called exactly once per process, not per thread.
|
|
//
|
|
int ShInitialize()
|
|
{
|
|
glslang::InitGlobalLock();
|
|
|
|
if (! InitProcess())
|
|
return 0;
|
|
|
|
glslang::GetGlobalLock();
|
|
++NumberOfClients;
|
|
glslang::ReleaseGlobalLock();
|
|
|
|
if (PerProcessGPA == nullptr)
|
|
PerProcessGPA = new TPoolAllocator();
|
|
|
|
glslang::TScanContext::fillInKeywordMap();
|
|
#ifdef ENABLE_HLSL
|
|
glslang::HlslScanContext::fillInKeywordMap();
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Driver calls these to create and destroy compiler/linker
|
|
// objects.
|
|
//
|
|
|
|
ShHandle ShConstructCompiler(const EShLanguage language, int debugOptions)
|
|
{
|
|
if (!InitThread())
|
|
return 0;
|
|
|
|
TShHandleBase* base = static_cast<TShHandleBase*>(ConstructCompiler(language, debugOptions));
|
|
|
|
return reinterpret_cast<void*>(base);
|
|
}
|
|
|
|
ShHandle ShConstructLinker(const EShExecutable executable, int debugOptions)
|
|
{
|
|
if (!InitThread())
|
|
return 0;
|
|
|
|
TShHandleBase* base = static_cast<TShHandleBase*>(ConstructLinker(executable, debugOptions));
|
|
|
|
return reinterpret_cast<void*>(base);
|
|
}
|
|
|
|
ShHandle ShConstructUniformMap()
|
|
{
|
|
if (!InitThread())
|
|
return 0;
|
|
|
|
TShHandleBase* base = static_cast<TShHandleBase*>(ConstructUniformMap());
|
|
|
|
return reinterpret_cast<void*>(base);
|
|
}
|
|
|
|
void ShDestruct(ShHandle handle)
|
|
{
|
|
if (handle == 0)
|
|
return;
|
|
|
|
TShHandleBase* base = static_cast<TShHandleBase*>(handle);
|
|
|
|
if (base->getAsCompiler())
|
|
DeleteCompiler(base->getAsCompiler());
|
|
else if (base->getAsLinker())
|
|
DeleteLinker(base->getAsLinker());
|
|
else if (base->getAsUniformMap())
|
|
DeleteUniformMap(base->getAsUniformMap());
|
|
}
|
|
|
|
//
|
|
// Cleanup symbol tables
|
|
//
|
|
int ShFinalize()
|
|
{
|
|
glslang::GetGlobalLock();
|
|
--NumberOfClients;
|
|
assert(NumberOfClients >= 0);
|
|
bool finalize = NumberOfClients == 0;
|
|
glslang::ReleaseGlobalLock();
|
|
if (! finalize)
|
|
return 1;
|
|
|
|
for (int version = 0; version < VersionCount; ++version) {
|
|
for (int spvVersion = 0; spvVersion < SpvVersionCount; ++spvVersion) {
|
|
for (int p = 0; p < ProfileCount; ++p) {
|
|
for (int source = 0; source < SourceCount; ++source) {
|
|
for (int stage = 0; stage < EShLangCount; ++stage) {
|
|
delete SharedSymbolTables[version][spvVersion][p][source][stage];
|
|
SharedSymbolTables[version][spvVersion][p][source][stage] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int version = 0; version < VersionCount; ++version) {
|
|
for (int spvVersion = 0; spvVersion < SpvVersionCount; ++spvVersion) {
|
|
for (int p = 0; p < ProfileCount; ++p) {
|
|
for (int source = 0; source < SourceCount; ++source) {
|
|
for (int pc = 0; pc < EPcCount; ++pc) {
|
|
delete CommonSymbolTable[version][spvVersion][p][source][pc];
|
|
CommonSymbolTable[version][spvVersion][p][source][pc] = 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (PerProcessGPA != nullptr) {
|
|
delete PerProcessGPA;
|
|
PerProcessGPA = nullptr;
|
|
}
|
|
|
|
glslang::TScanContext::deleteKeywordMap();
|
|
#ifdef ENABLE_HLSL
|
|
glslang::HlslScanContext::deleteKeywordMap();
|
|
#endif
|
|
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Do a full compile on the given strings for a single compilation unit
|
|
// forming a complete stage. The result of the machine dependent compilation
|
|
// is left in the provided compile object.
|
|
//
|
|
// Return: The return value is really boolean, indicating
|
|
// success (1) or failure (0).
|
|
//
|
|
int ShCompile(
|
|
const ShHandle handle,
|
|
const char* const shaderStrings[],
|
|
const int numStrings,
|
|
const int* inputLengths,
|
|
const EShOptimizationLevel optLevel,
|
|
const TBuiltInResource* resources,
|
|
int /*debugOptions*/,
|
|
int defaultVersion, // use 100 for ES environment, 110 for desktop
|
|
bool forwardCompatible, // give errors for use of deprecated features
|
|
EShMessages messages // warnings/errors/AST; things to print out
|
|
)
|
|
{
|
|
// Map the generic handle to the C++ object
|
|
if (handle == 0)
|
|
return 0;
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
|
|
TCompiler* compiler = base->getAsCompiler();
|
|
if (compiler == 0)
|
|
return 0;
|
|
|
|
SetThreadPoolAllocator(compiler->getPool());
|
|
|
|
compiler->infoSink.info.erase();
|
|
compiler->infoSink.debug.erase();
|
|
|
|
TIntermediate intermediate(compiler->getLanguage());
|
|
TShader::ForbidIncluder includer;
|
|
bool success = CompileDeferred(compiler, shaderStrings, numStrings, inputLengths, nullptr,
|
|
"", optLevel, resources, defaultVersion, ENoProfile, false,
|
|
forwardCompatible, messages, intermediate, includer);
|
|
|
|
//
|
|
// Call the machine dependent compiler
|
|
//
|
|
if (success && intermediate.getTreeRoot() && optLevel != EShOptNoGeneration)
|
|
success = compiler->compile(intermediate.getTreeRoot(), intermediate.getVersion(), intermediate.getProfile());
|
|
|
|
intermediate.removeTree();
|
|
|
|
// Throw away all the temporary memory used by the compilation process.
|
|
// The push was done in the CompileDeferred() call above.
|
|
GetThreadPoolAllocator().pop();
|
|
|
|
return success ? 1 : 0;
|
|
}
|
|
|
|
//
|
|
// Link the given compile objects.
|
|
//
|
|
// Return: The return value of is really boolean, indicating
|
|
// success or failure.
|
|
//
|
|
int ShLinkExt(
|
|
const ShHandle linkHandle,
|
|
const ShHandle compHandles[],
|
|
const int numHandles)
|
|
{
|
|
if (linkHandle == 0 || numHandles == 0)
|
|
return 0;
|
|
|
|
THandleList cObjects;
|
|
|
|
for (int i = 0; i < numHandles; ++i) {
|
|
if (compHandles[i] == 0)
|
|
return 0;
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(compHandles[i]);
|
|
if (base->getAsLinker()) {
|
|
cObjects.push_back(base->getAsLinker());
|
|
}
|
|
if (base->getAsCompiler())
|
|
cObjects.push_back(base->getAsCompiler());
|
|
|
|
if (cObjects[i] == 0)
|
|
return 0;
|
|
}
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(linkHandle);
|
|
TLinker* linker = static_cast<TLinker*>(base->getAsLinker());
|
|
|
|
SetThreadPoolAllocator(linker->getPool());
|
|
|
|
if (linker == 0)
|
|
return 0;
|
|
|
|
linker->infoSink.info.erase();
|
|
|
|
for (int i = 0; i < numHandles; ++i) {
|
|
if (cObjects[i]->getAsCompiler()) {
|
|
if (! cObjects[i]->getAsCompiler()->linkable()) {
|
|
linker->infoSink.info.message(EPrefixError, "Not all shaders have valid object code.");
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ret = linker->link(cObjects);
|
|
|
|
return ret ? 1 : 0;
|
|
}
|
|
|
|
//
|
|
// ShSetEncrpytionMethod is a place-holder for specifying
|
|
// how source code is encrypted.
|
|
//
|
|
void ShSetEncryptionMethod(ShHandle handle)
|
|
{
|
|
if (handle == 0)
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Return any compiler/linker/uniformmap log of messages for the application.
|
|
//
|
|
const char* ShGetInfoLog(const ShHandle handle)
|
|
{
|
|
if (handle == 0)
|
|
return 0;
|
|
|
|
TShHandleBase* base = static_cast<TShHandleBase*>(handle);
|
|
TInfoSink* infoSink;
|
|
|
|
if (base->getAsCompiler())
|
|
infoSink = &(base->getAsCompiler()->getInfoSink());
|
|
else if (base->getAsLinker())
|
|
infoSink = &(base->getAsLinker()->getInfoSink());
|
|
else
|
|
return 0;
|
|
|
|
infoSink->info << infoSink->debug.c_str();
|
|
return infoSink->info.c_str();
|
|
}
|
|
|
|
//
|
|
// Return the resulting binary code from the link process. Structure
|
|
// is machine dependent.
|
|
//
|
|
const void* ShGetExecutable(const ShHandle handle)
|
|
{
|
|
if (handle == 0)
|
|
return 0;
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
|
|
|
|
TLinker* linker = static_cast<TLinker*>(base->getAsLinker());
|
|
if (linker == 0)
|
|
return 0;
|
|
|
|
return linker->getObjectCode();
|
|
}
|
|
|
|
//
|
|
// Let the linker know where the application said it's attributes are bound.
|
|
// The linker does not use these values, they are remapped by the ICD or
|
|
// hardware. It just needs them to know what's aliased.
|
|
//
|
|
// Return: The return value of is really boolean, indicating
|
|
// success or failure.
|
|
//
|
|
int ShSetVirtualAttributeBindings(const ShHandle handle, const ShBindingTable* table)
|
|
{
|
|
if (handle == 0)
|
|
return 0;
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
|
|
TLinker* linker = static_cast<TLinker*>(base->getAsLinker());
|
|
|
|
if (linker == 0)
|
|
return 0;
|
|
|
|
linker->setAppAttributeBindings(table);
|
|
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Let the linker know where the predefined attributes have to live.
|
|
//
|
|
int ShSetFixedAttributeBindings(const ShHandle handle, const ShBindingTable* table)
|
|
{
|
|
if (handle == 0)
|
|
return 0;
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
|
|
TLinker* linker = static_cast<TLinker*>(base->getAsLinker());
|
|
|
|
if (linker == 0)
|
|
return 0;
|
|
|
|
linker->setFixedAttributeBindings(table);
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Some attribute locations are off-limits to the linker...
|
|
//
|
|
int ShExcludeAttributes(const ShHandle handle, int *attributes, int count)
|
|
{
|
|
if (handle == 0)
|
|
return 0;
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
|
|
TLinker* linker = static_cast<TLinker*>(base->getAsLinker());
|
|
if (linker == 0)
|
|
return 0;
|
|
|
|
linker->setExcludedAttributes(attributes, count);
|
|
|
|
return 1;
|
|
}
|
|
|
|
//
|
|
// Return the index for OpenGL to use for knowing where a uniform lives.
|
|
//
|
|
// Return: The return value of is really boolean, indicating
|
|
// success or failure.
|
|
//
|
|
int ShGetUniformLocation(const ShHandle handle, const char* name)
|
|
{
|
|
if (handle == 0)
|
|
return -1;
|
|
|
|
TShHandleBase* base = reinterpret_cast<TShHandleBase*>(handle);
|
|
TUniformMap* uniformMap= base->getAsUniformMap();
|
|
if (uniformMap == 0)
|
|
return -1;
|
|
|
|
return uniformMap->getLocation(name);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Deferred-Lowering C++ Interface
|
|
// -----------------------------------
|
|
//
|
|
// Below is a new alternate C++ interface that might potentially replace the above
|
|
// opaque handle-based interface.
|
|
//
|
|
// See more detailed comment in ShaderLang.h
|
|
//
|
|
|
|
namespace glslang {
|
|
|
|
Version GetVersion()
|
|
{
|
|
Version version;
|
|
version.major = GLSLANG_VERSION_MAJOR;
|
|
version.minor = GLSLANG_VERSION_MINOR;
|
|
version.patch = GLSLANG_VERSION_PATCH;
|
|
version.flavor = GLSLANG_VERSION_FLAVOR;
|
|
return version;
|
|
}
|
|
|
|
#define QUOTE(s) #s
|
|
#define STR(n) QUOTE(n)
|
|
|
|
const char* GetEsslVersionString()
|
|
{
|
|
return "OpenGL ES GLSL 3.20 glslang Khronos. " STR(GLSLANG_VERSION_MAJOR) "." STR(GLSLANG_VERSION_MINOR) "." STR(
|
|
GLSLANG_VERSION_PATCH) GLSLANG_VERSION_FLAVOR;
|
|
}
|
|
|
|
const char* GetGlslVersionString()
|
|
{
|
|
return "4.60 glslang Khronos. " STR(GLSLANG_VERSION_MAJOR) "." STR(GLSLANG_VERSION_MINOR) "." STR(
|
|
GLSLANG_VERSION_PATCH) GLSLANG_VERSION_FLAVOR;
|
|
}
|
|
|
|
int GetKhronosToolId()
|
|
{
|
|
return 8;
|
|
}
|
|
|
|
bool InitializeProcess()
|
|
{
|
|
return ShInitialize() != 0;
|
|
}
|
|
|
|
void FinalizeProcess()
|
|
{
|
|
ShFinalize();
|
|
}
|
|
|
|
class TDeferredCompiler : public TCompiler {
|
|
public:
|
|
TDeferredCompiler(EShLanguage s, TInfoSink& i) : TCompiler(s, i) { }
|
|
virtual bool compile(TIntermNode*, int = 0, EProfile = ENoProfile) { return true; }
|
|
};
|
|
|
|
TShader::TShader(EShLanguage s)
|
|
: stage(s), lengths(nullptr), stringNames(nullptr), preamble("")
|
|
{
|
|
pool = new TPoolAllocator;
|
|
infoSink = new TInfoSink;
|
|
compiler = new TDeferredCompiler(stage, *infoSink);
|
|
intermediate = new TIntermediate(s);
|
|
|
|
// clear environment (avoid constructors in them for use in a C interface)
|
|
environment.input.languageFamily = EShSourceNone;
|
|
environment.input.dialect = EShClientNone;
|
|
environment.input.vulkanRulesRelaxed = false;
|
|
environment.client.client = EShClientNone;
|
|
environment.target.language = EShTargetNone;
|
|
environment.target.hlslFunctionality1 = false;
|
|
}
|
|
|
|
TShader::~TShader()
|
|
{
|
|
delete infoSink;
|
|
delete compiler;
|
|
delete intermediate;
|
|
delete pool;
|
|
}
|
|
|
|
void TShader::setStrings(const char* const* s, int n)
|
|
{
|
|
strings = s;
|
|
numStrings = n;
|
|
lengths = nullptr;
|
|
}
|
|
|
|
void TShader::setStringsWithLengths(const char* const* s, const int* l, int n)
|
|
{
|
|
strings = s;
|
|
numStrings = n;
|
|
lengths = l;
|
|
}
|
|
|
|
void TShader::setStringsWithLengthsAndNames(
|
|
const char* const* s, const int* l, const char* const* names, int n)
|
|
{
|
|
strings = s;
|
|
numStrings = n;
|
|
lengths = l;
|
|
stringNames = names;
|
|
}
|
|
|
|
void TShader::setEntryPoint(const char* entryPoint)
|
|
{
|
|
intermediate->setEntryPointName(entryPoint);
|
|
}
|
|
|
|
void TShader::setSourceEntryPoint(const char* name)
|
|
{
|
|
sourceEntryPointName = name;
|
|
}
|
|
|
|
// Log initial settings and transforms.
|
|
// See comment for class TProcesses.
|
|
void TShader::addProcesses(const std::vector<std::string>& p)
|
|
{
|
|
intermediate->addProcesses(p);
|
|
}
|
|
|
|
void TShader::setUniqueId(unsigned long long id)
|
|
{
|
|
intermediate->setUniqueId(id);
|
|
}
|
|
|
|
void TShader::setInvertY(bool invert) { intermediate->setInvertY(invert); }
|
|
void TShader::setNanMinMaxClamp(bool useNonNan) { intermediate->setNanMinMaxClamp(useNonNan); }
|
|
|
|
#ifndef GLSLANG_WEB
|
|
|
|
// Set binding base for given resource type
|
|
void TShader::setShiftBinding(TResourceType res, unsigned int base) {
|
|
intermediate->setShiftBinding(res, base);
|
|
}
|
|
|
|
// Set binding base for given resource type for a given binding set.
|
|
void TShader::setShiftBindingForSet(TResourceType res, unsigned int base, unsigned int set) {
|
|
intermediate->setShiftBindingForSet(res, base, set);
|
|
}
|
|
|
|
// Set binding base for sampler types
|
|
void TShader::setShiftSamplerBinding(unsigned int base) { setShiftBinding(EResSampler, base); }
|
|
// Set binding base for texture types (SRV)
|
|
void TShader::setShiftTextureBinding(unsigned int base) { setShiftBinding(EResTexture, base); }
|
|
// Set binding base for image types
|
|
void TShader::setShiftImageBinding(unsigned int base) { setShiftBinding(EResImage, base); }
|
|
// Set binding base for uniform buffer objects (CBV)
|
|
void TShader::setShiftUboBinding(unsigned int base) { setShiftBinding(EResUbo, base); }
|
|
// Synonym for setShiftUboBinding, to match HLSL language.
|
|
void TShader::setShiftCbufferBinding(unsigned int base) { setShiftBinding(EResUbo, base); }
|
|
// Set binding base for UAV (unordered access view)
|
|
void TShader::setShiftUavBinding(unsigned int base) { setShiftBinding(EResUav, base); }
|
|
// Set binding base for SSBOs
|
|
void TShader::setShiftSsboBinding(unsigned int base) { setShiftBinding(EResSsbo, base); }
|
|
// Enables binding automapping using TIoMapper
|
|
void TShader::setAutoMapBindings(bool map) { intermediate->setAutoMapBindings(map); }
|
|
// Enables position.Y output negation in vertex shader
|
|
|
|
// Fragile: currently within one stage: simple auto-assignment of location
|
|
void TShader::setAutoMapLocations(bool map) { intermediate->setAutoMapLocations(map); }
|
|
void TShader::addUniformLocationOverride(const char* name, int loc)
|
|
{
|
|
intermediate->addUniformLocationOverride(name, loc);
|
|
}
|
|
void TShader::setUniformLocationBase(int base)
|
|
{
|
|
intermediate->setUniformLocationBase(base);
|
|
}
|
|
void TShader::setNoStorageFormat(bool useUnknownFormat) { intermediate->setNoStorageFormat(useUnknownFormat); }
|
|
void TShader::setResourceSetBinding(const std::vector<std::string>& base) { intermediate->setResourceSetBinding(base); }
|
|
void TShader::setTextureSamplerTransformMode(EShTextureSamplerTransformMode mode) { intermediate->setTextureSamplerTransformMode(mode); }
|
|
#endif
|
|
|
|
void TShader::addBlockStorageOverride(const char* nameStr, TBlockStorageClass backing) { intermediate->addBlockStorageOverride(nameStr, backing); }
|
|
|
|
void TShader::setGlobalUniformBlockName(const char* name) { intermediate->setGlobalUniformBlockName(name); }
|
|
void TShader::setGlobalUniformSet(unsigned int set) { intermediate->setGlobalUniformSet(set); }
|
|
void TShader::setGlobalUniformBinding(unsigned int binding) { intermediate->setGlobalUniformBinding(binding); }
|
|
|
|
void TShader::setAtomicCounterBlockName(const char* name) { intermediate->setAtomicCounterBlockName(name); }
|
|
void TShader::setAtomicCounterBlockSet(unsigned int set) { intermediate->setAtomicCounterBlockSet(set); }
|
|
|
|
#ifdef ENABLE_HLSL
|
|
// See comment above TDefaultHlslIoMapper in iomapper.cpp:
|
|
void TShader::setHlslIoMapping(bool hlslIoMap) { intermediate->setHlslIoMapping(hlslIoMap); }
|
|
void TShader::setFlattenUniformArrays(bool flatten) { intermediate->setFlattenUniformArrays(flatten); }
|
|
#endif
|
|
|
|
//
|
|
// Turn the shader strings into a parse tree in the TIntermediate.
|
|
//
|
|
// Returns true for success.
|
|
//
|
|
bool TShader::parse(const TBuiltInResource* builtInResources, int defaultVersion, EProfile defaultProfile, bool forceDefaultVersionAndProfile,
|
|
bool forwardCompatible, EShMessages messages, Includer& includer)
|
|
{
|
|
if (! InitThread())
|
|
return false;
|
|
SetThreadPoolAllocator(pool);
|
|
|
|
if (! preamble)
|
|
preamble = "";
|
|
|
|
return CompileDeferred(compiler, strings, numStrings, lengths, stringNames,
|
|
preamble, EShOptNone, builtInResources, defaultVersion,
|
|
defaultProfile, forceDefaultVersionAndProfile,
|
|
forwardCompatible, messages, *intermediate, includer, sourceEntryPointName,
|
|
&environment);
|
|
}
|
|
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
// Fill in a string with the result of preprocessing ShaderStrings
|
|
// Returns true if all extensions, pragmas and version strings were valid.
|
|
//
|
|
// NOTE: Doing just preprocessing to obtain a correct preprocessed shader string
|
|
// is not an officially supported or fully working path.
|
|
bool TShader::preprocess(const TBuiltInResource* builtInResources,
|
|
int defaultVersion, EProfile defaultProfile,
|
|
bool forceDefaultVersionAndProfile,
|
|
bool forwardCompatible, EShMessages message,
|
|
std::string* output_string,
|
|
Includer& includer)
|
|
{
|
|
if (! InitThread())
|
|
return false;
|
|
SetThreadPoolAllocator(pool);
|
|
|
|
if (! preamble)
|
|
preamble = "";
|
|
|
|
return PreprocessDeferred(compiler, strings, numStrings, lengths, stringNames, preamble,
|
|
EShOptNone, builtInResources, defaultVersion,
|
|
defaultProfile, forceDefaultVersionAndProfile,
|
|
forwardCompatible, message, includer, *intermediate, output_string,
|
|
&environment);
|
|
}
|
|
#endif
|
|
|
|
const char* TShader::getInfoLog()
|
|
{
|
|
return infoSink->info.c_str();
|
|
}
|
|
|
|
const char* TShader::getInfoDebugLog()
|
|
{
|
|
return infoSink->debug.c_str();
|
|
}
|
|
|
|
TProgram::TProgram() :
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
reflection(0),
|
|
#endif
|
|
linked(false)
|
|
{
|
|
pool = new TPoolAllocator;
|
|
infoSink = new TInfoSink;
|
|
for (int s = 0; s < EShLangCount; ++s) {
|
|
intermediate[s] = 0;
|
|
newedIntermediate[s] = false;
|
|
}
|
|
}
|
|
|
|
TProgram::~TProgram()
|
|
{
|
|
delete infoSink;
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
delete reflection;
|
|
#endif
|
|
|
|
for (int s = 0; s < EShLangCount; ++s)
|
|
if (newedIntermediate[s])
|
|
delete intermediate[s];
|
|
|
|
delete pool;
|
|
}
|
|
|
|
//
|
|
// Merge the compilation units within each stage into a single TIntermediate.
|
|
// All starting compilation units need to be the result of calling TShader::parse().
|
|
//
|
|
// Return true for success.
|
|
//
|
|
bool TProgram::link(EShMessages messages)
|
|
{
|
|
if (linked)
|
|
return false;
|
|
linked = true;
|
|
|
|
bool error = false;
|
|
|
|
SetThreadPoolAllocator(pool);
|
|
|
|
for (int s = 0; s < EShLangCount; ++s) {
|
|
if (! linkStage((EShLanguage)s, messages))
|
|
error = true;
|
|
}
|
|
|
|
if (!error) {
|
|
if (! crossStageCheck(messages))
|
|
error = true;
|
|
}
|
|
|
|
return ! error;
|
|
}
|
|
|
|
//
|
|
// Merge the compilation units within the given stage into a single TIntermediate.
|
|
//
|
|
// Return true for success.
|
|
//
|
|
bool TProgram::linkStage(EShLanguage stage, EShMessages messages)
|
|
{
|
|
if (stages[stage].size() == 0)
|
|
return true;
|
|
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
int numEsShaders = 0, numNonEsShaders = 0;
|
|
for (auto it = stages[stage].begin(); it != stages[stage].end(); ++it) {
|
|
if ((*it)->intermediate->getProfile() == EEsProfile) {
|
|
numEsShaders++;
|
|
} else {
|
|
numNonEsShaders++;
|
|
}
|
|
}
|
|
|
|
if (numEsShaders > 0 && numNonEsShaders > 0) {
|
|
infoSink->info.message(EPrefixError, "Cannot mix ES profile with non-ES profile shaders");
|
|
return false;
|
|
} else if (numEsShaders > 1) {
|
|
infoSink->info.message(EPrefixError, "Cannot attach multiple ES shaders of the same type to a single program");
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Be efficient for the common single compilation unit per stage case,
|
|
// reusing it's TIntermediate instead of merging into a new one.
|
|
//
|
|
TIntermediate *firstIntermediate = stages[stage].front()->intermediate;
|
|
if (stages[stage].size() == 1)
|
|
intermediate[stage] = firstIntermediate;
|
|
else {
|
|
intermediate[stage] = new TIntermediate(stage,
|
|
firstIntermediate->getVersion(),
|
|
firstIntermediate->getProfile());
|
|
intermediate[stage]->setLimits(firstIntermediate->getLimits());
|
|
|
|
// The new TIntermediate must use the same origin as the original TIntermediates.
|
|
// Otherwise linking will fail due to different coordinate systems.
|
|
if (firstIntermediate->getOriginUpperLeft()) {
|
|
intermediate[stage]->setOriginUpperLeft();
|
|
}
|
|
intermediate[stage]->setSpv(firstIntermediate->getSpv());
|
|
|
|
newedIntermediate[stage] = true;
|
|
}
|
|
|
|
if (messages & EShMsgAST)
|
|
infoSink->info << "\nLinked " << StageName(stage) << " stage:\n\n";
|
|
|
|
if (stages[stage].size() > 1) {
|
|
std::list<TShader*>::const_iterator it;
|
|
for (it = stages[stage].begin(); it != stages[stage].end(); ++it)
|
|
intermediate[stage]->merge(*infoSink, *(*it)->intermediate);
|
|
}
|
|
#else
|
|
intermediate[stage] = stages[stage].front()->intermediate;
|
|
#endif
|
|
intermediate[stage]->finalCheck(*infoSink, (messages & EShMsgKeepUncalled) != 0);
|
|
|
|
#ifndef GLSLANG_ANGLE
|
|
if (messages & EShMsgAST)
|
|
intermediate[stage]->output(*infoSink, true);
|
|
#endif
|
|
|
|
return intermediate[stage]->getNumErrors() == 0;
|
|
}
|
|
|
|
//
|
|
// Check that there are no errors in linker objects accross stages
|
|
//
|
|
// Return true if no errors.
|
|
//
|
|
bool TProgram::crossStageCheck(EShMessages) {
|
|
|
|
// make temporary intermediates to hold the linkage symbols for each linking interface
|
|
// while we do the checks
|
|
// Independent interfaces are:
|
|
// all uniform variables and blocks
|
|
// all buffer blocks
|
|
// all in/out on a stage boundary
|
|
|
|
TVector<TIntermediate*> activeStages;
|
|
for (int s = 0; s < EShLangCount; ++s) {
|
|
if (intermediate[s])
|
|
activeStages.push_back(intermediate[s]);
|
|
}
|
|
|
|
// no extra linking if there is only one stage
|
|
if (! (activeStages.size() > 1))
|
|
return true;
|
|
|
|
// setup temporary tree to hold unfirom objects from different stages
|
|
TIntermediate* firstIntermediate = activeStages.front();
|
|
TIntermediate uniforms(EShLangCount,
|
|
firstIntermediate->getVersion(),
|
|
firstIntermediate->getProfile());
|
|
uniforms.setSpv(firstIntermediate->getSpv());
|
|
|
|
TIntermAggregate uniformObjects(EOpLinkerObjects);
|
|
TIntermAggregate root(EOpSequence);
|
|
root.getSequence().push_back(&uniformObjects);
|
|
uniforms.setTreeRoot(&root);
|
|
|
|
bool error = false;
|
|
|
|
// merge uniforms from all stages into a single intermediate
|
|
for (unsigned int i = 0; i < activeStages.size(); ++i) {
|
|
uniforms.mergeUniformObjects(*infoSink, *activeStages[i]);
|
|
}
|
|
error |= uniforms.getNumErrors() != 0;
|
|
|
|
// copy final definition of global block back into each stage
|
|
for (unsigned int i = 0; i < activeStages.size(); ++i) {
|
|
// We only want to merge into already existing global uniform blocks.
|
|
// A stage that doesn't already know about the global doesn't care about it's content.
|
|
// Otherwise we end up pointing to the same object between different stages
|
|
// and that will break binding/set remappings
|
|
bool mergeExistingOnly = true;
|
|
activeStages[i]->mergeGlobalUniformBlocks(*infoSink, uniforms, mergeExistingOnly);
|
|
}
|
|
|
|
// compare cross stage symbols for each stage boundary
|
|
for (unsigned int i = 1; i < activeStages.size(); ++i) {
|
|
activeStages[i - 1]->checkStageIO(*infoSink, *activeStages[i]);
|
|
error |= (activeStages[i - 1]->getNumErrors() != 0);
|
|
}
|
|
|
|
return !error;
|
|
}
|
|
|
|
const char* TProgram::getInfoLog()
|
|
{
|
|
return infoSink->info.c_str();
|
|
}
|
|
|
|
const char* TProgram::getInfoDebugLog()
|
|
{
|
|
return infoSink->debug.c_str();
|
|
}
|
|
|
|
#if !defined(GLSLANG_WEB) && !defined(GLSLANG_ANGLE)
|
|
|
|
//
|
|
// Reflection implementation.
|
|
//
|
|
|
|
bool TProgram::buildReflection(int opts)
|
|
{
|
|
if (! linked || reflection != nullptr)
|
|
return false;
|
|
|
|
int firstStage = EShLangVertex, lastStage = EShLangFragment;
|
|
|
|
if (opts & EShReflectionIntermediateIO) {
|
|
// if we're reflecting intermediate I/O, determine the first and last stage linked and use those as the
|
|
// boundaries for which stages generate pipeline inputs/outputs
|
|
firstStage = EShLangCount;
|
|
lastStage = 0;
|
|
for (int s = 0; s < EShLangCount; ++s) {
|
|
if (intermediate[s]) {
|
|
firstStage = std::min(firstStage, s);
|
|
lastStage = std::max(lastStage, s);
|
|
}
|
|
}
|
|
}
|
|
|
|
reflection = new TReflection((EShReflectionOptions)opts, (EShLanguage)firstStage, (EShLanguage)lastStage);
|
|
|
|
for (int s = 0; s < EShLangCount; ++s) {
|
|
if (intermediate[s]) {
|
|
if (! reflection->addStage((EShLanguage)s, *intermediate[s]))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
unsigned TProgram::getLocalSize(int dim) const { return reflection->getLocalSize(dim); }
|
|
int TProgram::getReflectionIndex(const char* name) const { return reflection->getIndex(name); }
|
|
int TProgram::getReflectionPipeIOIndex(const char* name, const bool inOrOut) const
|
|
{ return reflection->getPipeIOIndex(name, inOrOut); }
|
|
|
|
int TProgram::getNumUniformVariables() const { return reflection->getNumUniforms(); }
|
|
const TObjectReflection& TProgram::getUniform(int index) const { return reflection->getUniform(index); }
|
|
int TProgram::getNumUniformBlocks() const { return reflection->getNumUniformBlocks(); }
|
|
const TObjectReflection& TProgram::getUniformBlock(int index) const { return reflection->getUniformBlock(index); }
|
|
int TProgram::getNumPipeInputs() const { return reflection->getNumPipeInputs(); }
|
|
const TObjectReflection& TProgram::getPipeInput(int index) const { return reflection->getPipeInput(index); }
|
|
int TProgram::getNumPipeOutputs() const { return reflection->getNumPipeOutputs(); }
|
|
const TObjectReflection& TProgram::getPipeOutput(int index) const { return reflection->getPipeOutput(index); }
|
|
int TProgram::getNumBufferVariables() const { return reflection->getNumBufferVariables(); }
|
|
const TObjectReflection& TProgram::getBufferVariable(int index) const { return reflection->getBufferVariable(index); }
|
|
int TProgram::getNumBufferBlocks() const { return reflection->getNumStorageBuffers(); }
|
|
const TObjectReflection& TProgram::getBufferBlock(int index) const { return reflection->getStorageBufferBlock(index); }
|
|
int TProgram::getNumAtomicCounters() const { return reflection->getNumAtomicCounters(); }
|
|
const TObjectReflection& TProgram::getAtomicCounter(int index) const { return reflection->getAtomicCounter(index); }
|
|
void TProgram::dumpReflection() { if (reflection != nullptr) reflection->dump(); }
|
|
|
|
//
|
|
// I/O mapping implementation.
|
|
//
|
|
bool TProgram::mapIO(TIoMapResolver* pResolver, TIoMapper* pIoMapper)
|
|
{
|
|
if (! linked)
|
|
return false;
|
|
TIoMapper* ioMapper = nullptr;
|
|
TIoMapper defaultIOMapper;
|
|
if (pIoMapper == nullptr)
|
|
ioMapper = &defaultIOMapper;
|
|
else
|
|
ioMapper = pIoMapper;
|
|
for (int s = 0; s < EShLangCount; ++s) {
|
|
if (intermediate[s]) {
|
|
if (! ioMapper->addStage((EShLanguage)s, *intermediate[s], *infoSink, pResolver))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return ioMapper->doMap(pResolver, *infoSink);
|
|
}
|
|
|
|
#endif // !GLSLANG_WEB && !GLSLANG_ANGLE
|
|
|
|
} // end namespace glslang
|