|
|
|
@@ -182,529 +182,6 @@ namespace {
|
|
|
|
|
return std::all_of(token.text.begin() + 1, token.text.end(), IsIdentifierChar);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class TokenCursor {
|
|
|
|
|
public:
|
|
|
|
|
TokenCursor(const Vector<CodeToken>& tokens, SizeT position) : m_tokens(tokens), m_position(position) {}
|
|
|
|
|
|
|
|
|
|
bool Consume(const char* expected) {
|
|
|
|
|
if (m_position >= m_tokens.size() || m_tokens[m_position].text != expected) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
++m_position;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ConsumeAnyIdentifier(String& identifier) {
|
|
|
|
|
if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
identifier = m_tokens[m_position++].text;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ConsumeAnyIdentifier() {
|
|
|
|
|
if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position])) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
++m_position;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ConsumeIdentifier(const String& expected) {
|
|
|
|
|
if (m_position >= m_tokens.size() || !IsIdentifierToken(m_tokens[m_position]) ||
|
|
|
|
|
m_tokens[m_position].text != expected) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
++m_position;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SizeT Position() const { return m_position; }
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
const Vector<CodeToken>& m_tokens;
|
|
|
|
|
SizeT m_position;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
SizeT CountToken(const Vector<CodeToken>& tokens, const String& tokenText) {
|
|
|
|
|
return static_cast<SizeT>(std::count_if(tokens.begin(), tokens.end(),
|
|
|
|
|
[&](const CodeToken& token) { return token.text == tokenText; }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool HasIdentifierWithPrefixOutsideAllowed(const Vector<CodeToken>& tokens, const String& prefix,
|
|
|
|
|
std::initializer_list<const char*> allowedIdentifiers) {
|
|
|
|
|
return std::any_of(tokens.begin(), tokens.end(), [&](const CodeToken& token) {
|
|
|
|
|
if (!IsIdentifierToken(token) || !token.text.starts_with(prefix)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return std::none_of(allowedIdentifiers.begin(), allowedIdentifiers.end(),
|
|
|
|
|
[&](const char* allowed) { return token.text == allowed; });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MatchTokenSequence(const Vector<CodeToken>& tokens, SizeT position,
|
|
|
|
|
std::initializer_list<const char*> expected) {
|
|
|
|
|
if (position + expected.size() > tokens.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
for (const char* token : expected) {
|
|
|
|
|
if (tokens[position++].text != token) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SizeT CountTokenSequence(const Vector<CodeToken>& tokens,
|
|
|
|
|
std::initializer_list<const char*> expected) {
|
|
|
|
|
SizeT count = 0;
|
|
|
|
|
for (SizeT position = 0; position < tokens.size(); ++position) {
|
|
|
|
|
if (MatchTokenSequence(tokens, position, expected)) {
|
|
|
|
|
++count;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FindUniqueTokenSequence(const Vector<CodeToken>& tokens, const Vector<CodeToken>& expected,
|
|
|
|
|
SizeT& sourceBegin, SizeT& sourceEnd) {
|
|
|
|
|
if (expected.empty() || expected.size() > tokens.size()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SizeT matchCount = 0;
|
|
|
|
|
for (SizeT position = 0; position + expected.size() <= tokens.size(); ++position) {
|
|
|
|
|
bool matches = true;
|
|
|
|
|
for (SizeT expectedIndex = 0; expectedIndex < expected.size(); ++expectedIndex) {
|
|
|
|
|
if (tokens[position + expectedIndex].text != expected[expectedIndex].text) {
|
|
|
|
|
matches = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!matches) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
++matchCount;
|
|
|
|
|
sourceBegin = tokens[position].begin;
|
|
|
|
|
sourceEnd = tokens[position + expected.size() - 1].end;
|
|
|
|
|
}
|
|
|
|
|
return matchCount == 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool IsPowerOfTwo(Uint32 value) {
|
|
|
|
|
return value != 0u && (value & (value - 1u)) == 0u;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct LinearPrefixScanMatch {
|
|
|
|
|
SizeT sharedArraySizeBegin = 0;
|
|
|
|
|
SizeT sharedArraySizeEnd = 0;
|
|
|
|
|
SizeT scanBegin = 0;
|
|
|
|
|
SizeT scanEnd = 0;
|
|
|
|
|
String cache;
|
|
|
|
|
String importance;
|
|
|
|
|
String prefixSum;
|
|
|
|
|
String loopLength;
|
|
|
|
|
String loopIndex;
|
|
|
|
|
String sum;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool ParseLinearPrefixScanTemplate(const Vector<CodeToken>& tokens, LinearPrefixScanMatch& match) {
|
|
|
|
|
// The workaround deliberately recognizes one complete algorithm, not merely the
|
|
|
|
|
// subgroupInclusiveAdd token. Changing scratch storage is only safe when that storage is
|
|
|
|
|
// private to this scan and the workgroup has exactly 1024 X invocations.
|
|
|
|
|
SizeT localSizeDeclarationCount = 0;
|
|
|
|
|
for (SizeT i = 0; i < tokens.size(); ++i) {
|
|
|
|
|
if (MatchTokenSequence(tokens, i, {"layout", "(", "local_size_x", "=", "1024", ")", "in", ";"})) {
|
|
|
|
|
++localSizeDeclarationCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (localSizeDeclarationCount != 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SizeT sharedDeclarationIndex = String::npos;
|
|
|
|
|
SizeT sharedDeclarationCount = 0;
|
|
|
|
|
String cacheName;
|
|
|
|
|
for (SizeT i = 0; i + 6 < tokens.size(); ++i) {
|
|
|
|
|
if (tokens[i].text != "shared" || tokens[i + 1].text != "float" || !IsIdentifierToken(tokens[i + 2]) ||
|
|
|
|
|
tokens[i + 3].text != "[" || tokens[i + 4].text != "64" || tokens[i + 5].text != "]" ||
|
|
|
|
|
tokens[i + 6].text != ";") {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
++sharedDeclarationCount;
|
|
|
|
|
sharedDeclarationIndex = i;
|
|
|
|
|
cacheName = tokens[i + 2].text;
|
|
|
|
|
}
|
|
|
|
|
if (sharedDeclarationCount != 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SizeT scanTokenIndex = String::npos;
|
|
|
|
|
SizeT scanCount = 0;
|
|
|
|
|
for (SizeT i = 0; i + 7 < tokens.size(); ++i) {
|
|
|
|
|
if (tokens[i].text == "float" && IsIdentifierToken(tokens[i + 1]) && tokens[i + 2].text == "=" &&
|
|
|
|
|
tokens[i + 3].text == "subgroupInclusiveAdd" && tokens[i + 4].text == "(" &&
|
|
|
|
|
IsIdentifierToken(tokens[i + 5]) && tokens[i + 6].text == ")" && tokens[i + 7].text == ";") {
|
|
|
|
|
++scanCount;
|
|
|
|
|
scanTokenIndex = i;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (scanCount != 1 || sharedDeclarationIndex >= scanTokenIndex) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TokenCursor cursor(tokens, scanTokenIndex);
|
|
|
|
|
String prefixSum;
|
|
|
|
|
String importance;
|
|
|
|
|
String loopLength;
|
|
|
|
|
String loopIndex;
|
|
|
|
|
String sum;
|
|
|
|
|
if (!cursor.Consume("float") || !cursor.ConsumeAnyIdentifier(prefixSum) || !cursor.Consume("=") ||
|
|
|
|
|
!cursor.Consume("subgroupInclusiveAdd") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.ConsumeAnyIdentifier(importance) || !cursor.Consume(")") || !cursor.Consume(";") ||
|
|
|
|
|
!cursor.Consume("if") || !cursor.Consume("(") || !cursor.Consume("gl_SubgroupInvocationID") ||
|
|
|
|
|
!cursor.Consume("==") || !cursor.Consume("gl_SubgroupSize") || !cursor.Consume("-") ||
|
|
|
|
|
!cursor.Consume("1u") || !cursor.Consume(")") || !cursor.ConsumeIdentifier(cacheName) ||
|
|
|
|
|
!cursor.Consume("[") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume("]") || !cursor.Consume("=") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume(";") || !cursor.Consume("barrier") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("uint") ||
|
|
|
|
|
!cursor.ConsumeAnyIdentifier(loopLength) || !cursor.Consume("=") || !cursor.Consume("uint") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume("findMSB") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.Consume("gl_NumSubgroups") || !cursor.Consume(")") || !cursor.Consume(")") ||
|
|
|
|
|
!cursor.Consume(";") || !cursor.ConsumeIdentifier(loopLength) || !cursor.Consume("+=") ||
|
|
|
|
|
!cursor.Consume("uint") || !cursor.Consume("(") || !cursor.Consume("gl_NumSubgroups") ||
|
|
|
|
|
!cursor.Consume("-") || !cursor.Consume("(") || !cursor.Consume("1u") || !cursor.Consume("<<") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.ConsumeIdentifier(loopLength) || !cursor.Consume("-") ||
|
|
|
|
|
!cursor.Consume("1u") || !cursor.Consume(")") || !cursor.Consume(")") || !cursor.Consume(">") ||
|
|
|
|
|
!cursor.Consume("0u") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("for") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume("uint") || !cursor.ConsumeAnyIdentifier(loopIndex) ||
|
|
|
|
|
!cursor.Consume("=") || !cursor.Consume("0") || !cursor.Consume(";") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("<") || !cursor.ConsumeIdentifier(loopLength) ||
|
|
|
|
|
!cursor.Consume(";") || !cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("++") ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.Consume("{") || !cursor.Consume("if") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume("&") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.Consume("1u") || !cursor.Consume("<<") || !cursor.ConsumeIdentifier(loopIndex) ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.Consume(")") || !cursor.Consume(">") || !cursor.Consume("0u") ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.Consume("{") || !cursor.ConsumeIdentifier(prefixSum) ||
|
|
|
|
|
!cursor.Consume("+=") || !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume("gl_SubgroupID") || !cursor.Consume(">>") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(loopIndex) || !cursor.Consume("<<") || !cursor.ConsumeIdentifier(loopIndex) ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.Consume("-") || !cursor.Consume("1u") || !cursor.Consume("]") ||
|
|
|
|
|
!cursor.Consume(";") || !cursor.Consume("if") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.Consume("gl_SubgroupInvocationID") || !cursor.Consume("==") || !cursor.Consume("gl_SubgroupSize") ||
|
|
|
|
|
!cursor.Consume("-") || !cursor.Consume("1u") || !cursor.Consume(")") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") || !cursor.Consume("gl_SubgroupID") ||
|
|
|
|
|
!cursor.Consume("]") || !cursor.Consume("=") || !cursor.ConsumeIdentifier(prefixSum) ||
|
|
|
|
|
!cursor.Consume(";") || !cursor.Consume("}") || !cursor.Consume("barrier") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("}") || !cursor.Consume("if") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume("gl_LocalInvocationID") || !cursor.Consume(".") ||
|
|
|
|
|
!cursor.Consume("x") || !cursor.Consume("==") || !cursor.Consume("uint") || !cursor.Consume("(") ||
|
|
|
|
|
!cursor.Consume("1024") || !cursor.Consume("-") || !cursor.Consume("1") || !cursor.Consume(")") ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.ConsumeIdentifier(cacheName) || !cursor.Consume("[") ||
|
|
|
|
|
!cursor.Consume("0") || !cursor.Consume("]") || !cursor.Consume("=") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume(";") || !cursor.Consume("barrier") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume(")") || !cursor.Consume(";") || !cursor.Consume("float") ||
|
|
|
|
|
!cursor.ConsumeAnyIdentifier(sum) || !cursor.Consume("=") || !cursor.ConsumeIdentifier(cacheName) ||
|
|
|
|
|
!cursor.Consume("[") || !cursor.Consume("0") || !cursor.Consume("]") || !cursor.Consume(";")) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
const SizeT scanEndToken = cursor.Position() - 1;
|
|
|
|
|
|
|
|
|
|
// Require the scan's immediate consumer as well. This makes the match specific to a
|
|
|
|
|
// linear distribution warp, and avoids changing unrelated prefix scans which may rely on
|
|
|
|
|
// the implementation's native subgroup partitioning.
|
|
|
|
|
if (!cursor.Consume("float") || !cursor.ConsumeAnyIdentifier() || !cursor.Consume("=") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.ConsumeIdentifier(prefixSum) || !cursor.Consume("-") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(importance) || !cursor.Consume(")") || !cursor.Consume("/") ||
|
|
|
|
|
!cursor.ConsumeIdentifier(sum) || !cursor.Consume("-") || !cursor.Consume("float") ||
|
|
|
|
|
!cursor.Consume("(") || !cursor.Consume("gl_LocalInvocationID") || !cursor.Consume(".") ||
|
|
|
|
|
!cursor.Consume("x") || !cursor.Consume("+") || !cursor.Consume("1u") || !cursor.Consume(")") ||
|
|
|
|
|
!cursor.Consume("/") || !cursor.Consume("float") || !cursor.Consume("(") || !cursor.Consume("1024") ||
|
|
|
|
|
!cursor.Consume(")") || !cursor.Consume(";")) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No other use may share the scratch array, and no additional subgroup operation or
|
|
|
|
|
// builtin may silently retain native-64 semantics after this module becomes virtual-32.
|
|
|
|
|
if (CountToken(tokens, cacheName) != 6 || CountToken(tokens, "subgroupInclusiveAdd") != 1 ||
|
|
|
|
|
CountToken(tokens, "gl_SubgroupInvocationID") != 2 || CountToken(tokens, "gl_SubgroupSize") != 2 ||
|
|
|
|
|
CountToken(tokens, "gl_SubgroupID") != 4 || CountToken(tokens, "gl_NumSubgroups") != 2 ||
|
|
|
|
|
CountToken(tokens, "gl_LocalInvocationID") != 2 || CountToken(tokens, "barrier") != 3 ||
|
|
|
|
|
CountToken(tokens, "findMSB") != 1 ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "subgroup", {"subgroupInclusiveAdd"}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(
|
|
|
|
|
tokens, "gl_Subgroup",
|
|
|
|
|
{"gl_SubgroupInvocationID", "gl_SubgroupSize", "gl_SubgroupID", "gl_NumSubgroups"}) ||
|
|
|
|
|
// ARB/NV spellings of lane-width-sensitive builtins and functions
|
|
|
|
|
// (gl_SubGroupSizeARB, ballotARB, gl_WarpSizeNV, shuffleNV, ...) must block the
|
|
|
|
|
// rewrite just like their KHR counterparts: they would silently keep native-width
|
|
|
|
|
// semantics in a module rewritten to the virtual 32-lane model.
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SubGroup", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Warp", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Thread", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SMID", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "ballot", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "shuffle", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "readInvocation", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "readFirstInvocation", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "anyInvocation", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "allInvocations", {})) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The scan must be at the top level of the sole main() body. Its existing barriers already
|
|
|
|
|
// require uniform control flow; this check prevents us from introducing extra barriers in
|
|
|
|
|
// a nested branch or loop.
|
|
|
|
|
SizeT mainOpenBrace = String::npos;
|
|
|
|
|
SizeT mainCloseBrace = String::npos;
|
|
|
|
|
SizeT mainCount = 0;
|
|
|
|
|
for (SizeT i = 0; i + 4 < tokens.size(); ++i) {
|
|
|
|
|
if (!MatchTokenSequence(tokens, i, {"void", "main", "(", ")", "{"})) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
++mainCount;
|
|
|
|
|
mainOpenBrace = i + 4;
|
|
|
|
|
int depth = 1;
|
|
|
|
|
for (SizeT j = mainOpenBrace + 1; j < tokens.size(); ++j) {
|
|
|
|
|
if (tokens[j].text == "{")
|
|
|
|
|
++depth;
|
|
|
|
|
else if (tokens[j].text == "}" && --depth == 0) {
|
|
|
|
|
mainCloseBrace = j;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (mainCount != 1 || mainCloseBrace == String::npos || scanTokenIndex <= mainOpenBrace ||
|
|
|
|
|
scanEndToken >= mainCloseBrace) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
int depthAtScan = 1;
|
|
|
|
|
for (SizeT i = mainOpenBrace + 1; i < scanTokenIndex; ++i) {
|
|
|
|
|
if (tokens[i].text == "{")
|
|
|
|
|
++depthAtScan;
|
|
|
|
|
else if (tokens[i].text == "}")
|
|
|
|
|
--depthAtScan;
|
|
|
|
|
}
|
|
|
|
|
if (depthAtScan != 1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constexpr const char* injectedNames[] = {"mglPrefixScanLane", "mglVirtualSubgroupInvocation",
|
|
|
|
|
"mglVirtualSubgroup", "mglVirtualSubgroupBase",
|
|
|
|
|
"mglPrefixLane", "mglVirtualSubgroupCount"};
|
|
|
|
|
for (const char* injectedName : injectedNames) {
|
|
|
|
|
if (CountToken(tokens, injectedName) != 0) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match.sharedArraySizeBegin = tokens[sharedDeclarationIndex + 4].begin;
|
|
|
|
|
match.sharedArraySizeEnd = tokens[sharedDeclarationIndex + 4].end;
|
|
|
|
|
match.scanBegin = tokens[scanTokenIndex].begin;
|
|
|
|
|
match.scanEnd = tokens[scanEndToken].end;
|
|
|
|
|
match.cache = std::move(cacheName);
|
|
|
|
|
match.importance = std::move(importance);
|
|
|
|
|
match.prefixSum = std::move(prefixSum);
|
|
|
|
|
match.loopLength = std::move(loopLength);
|
|
|
|
|
match.loopIndex = std::move(loopIndex);
|
|
|
|
|
match.sum = std::move(sum);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String BuildLinearPrefixScanReplacement(const LinearPrefixScanMatch& match) {
|
|
|
|
|
String replacement;
|
|
|
|
|
replacement.reserve(1800);
|
|
|
|
|
replacement += "uint mglPrefixScanLane = gl_LocalInvocationID.x;\n";
|
|
|
|
|
replacement += "uint mglVirtualSubgroupInvocation = mglPrefixScanLane & 31u;\n";
|
|
|
|
|
replacement += "uint mglVirtualSubgroup = mglPrefixScanLane >> 5u;\n";
|
|
|
|
|
replacement += "const uint mglVirtualSubgroupCount = 32u;\n";
|
|
|
|
|
replacement += match.cache + "[mglPrefixScanLane] = " + match.importance + ";\n";
|
|
|
|
|
replacement += "barrier();\n";
|
|
|
|
|
replacement += "float " + match.prefixSum + " = 0.0f;\n";
|
|
|
|
|
replacement += "uint mglVirtualSubgroupBase = mglVirtualSubgroup << 5u;\n";
|
|
|
|
|
replacement += "for (uint mglPrefixLane = mglVirtualSubgroupBase; "
|
|
|
|
|
"mglPrefixLane <= mglPrefixScanLane; ++mglPrefixLane) {\n";
|
|
|
|
|
replacement += match.prefixSum + " += " + match.cache + "[mglPrefixLane];\n";
|
|
|
|
|
replacement += "}\n";
|
|
|
|
|
replacement += "barrier();\n";
|
|
|
|
|
replacement += "if (mglVirtualSubgroupInvocation == 31u) " + match.cache +
|
|
|
|
|
"[mglVirtualSubgroup] = " + match.prefixSum + ";\n";
|
|
|
|
|
replacement += "barrier();\n";
|
|
|
|
|
replacement += "uint " + match.loopLength + " = uint(findMSB(mglVirtualSubgroupCount));\n";
|
|
|
|
|
replacement +=
|
|
|
|
|
match.loopLength + " += uint(mglVirtualSubgroupCount - (1u << (" + match.loopLength + " - 1u)) > 0u);\n";
|
|
|
|
|
replacement += "for (uint " + match.loopIndex + " = 0u; " + match.loopIndex + " < " + match.loopLength +
|
|
|
|
|
"; ++" + match.loopIndex + ") {\n";
|
|
|
|
|
replacement += "if ((mglVirtualSubgroup & (1u << " + match.loopIndex + ")) > 0u) {\n";
|
|
|
|
|
replacement += match.prefixSum + " += " + match.cache + "[(mglVirtualSubgroup >> " + match.loopIndex + " << " +
|
|
|
|
|
match.loopIndex + ") - 1u];\n";
|
|
|
|
|
replacement += "if (mglVirtualSubgroupInvocation == 31u) " + match.cache +
|
|
|
|
|
"[mglVirtualSubgroup] = " + match.prefixSum + ";\n";
|
|
|
|
|
replacement += "}\nbarrier();\n}\n";
|
|
|
|
|
replacement += "if (mglPrefixScanLane == 1023u) " + match.cache + "[0] = " + match.prefixSum + ";\n";
|
|
|
|
|
replacement += "barrier();\n";
|
|
|
|
|
replacement += "float " + match.sum + " = " + match.cache + "[0];";
|
|
|
|
|
return replacement;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct WeightedExposureReductionMatch {
|
|
|
|
|
SizeT mainBegin = 0;
|
|
|
|
|
SizeT mainEnd = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
bool ParseWeightedExposureReductionTemplate(const Vector<CodeToken>& tokens,
|
|
|
|
|
WeightedExposureReductionMatch& match) {
|
|
|
|
|
// IterationRP's exposure pass is a complete, stable shader-pack template. Match the
|
|
|
|
|
// whole main body before replacing it: a partial match would be unsafe because the
|
|
|
|
|
// replacement deliberately replays the 32x16 sample grid from one invocation.
|
|
|
|
|
static const Vector<CodeToken> expectedMain = TokenizeCode(R"glsl(
|
|
|
|
|
void main() {
|
|
|
|
|
vec2 texCoord = (vec2(gl_GlobalInvocationID.xy) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f);
|
|
|
|
|
vec2 sampleCoord = texCoord * (1.0f / 64.0f);
|
|
|
|
|
sampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f;
|
|
|
|
|
float tileExposure = dot(textureLod(colortex2, sampleCoord, 0.0f).rgb, vec3(0.2125f, 0.7154f, 0.0721f));
|
|
|
|
|
vec2 sampleLuminance = vec2(tileExposure, 0.0f);
|
|
|
|
|
sampleLuminance = subgroupInclusiveAdd(sampleLuminance);
|
|
|
|
|
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleLuminance;
|
|
|
|
|
barrier();
|
|
|
|
|
uint loopLength = uint(findMSB(gl_NumSubgroups));
|
|
|
|
|
loopLength += uint(gl_NumSubgroups - (1u << (loopLength - 1u)) > 0u);
|
|
|
|
|
for (uint i = 0; i < loopLength; i++) {
|
|
|
|
|
if ((gl_SubgroupID & (1u << i)) > 0u) {
|
|
|
|
|
sampleLuminance += prefixSumCache[(gl_SubgroupID >> i << i) - 1u];
|
|
|
|
|
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleLuminance;
|
|
|
|
|
}
|
|
|
|
|
barrier();
|
|
|
|
|
}
|
|
|
|
|
if (gl_LocalInvocationIndex == 511u) prefixSumCache[0] = sampleLuminance / 512.0f;
|
|
|
|
|
;
|
|
|
|
|
barrier();
|
|
|
|
|
float avg = prefixSumCache[0].x;
|
|
|
|
|
vec2 tileDistance = texCoord * 2.0f - 1.0f;
|
|
|
|
|
tileDistance.y /= aspectRatio;
|
|
|
|
|
float centerDistance = length(tileDistance);
|
|
|
|
|
float tileWeight = remapSaturate(centerDistance, 0.6f, 0.4f);
|
|
|
|
|
tileExposure = max(7.0E-7f, tileExposure);
|
|
|
|
|
float lumaWeight = avg / tileExposure;
|
|
|
|
|
lumaWeight = pow(lumaWeight, remapSaturate(avg, 0.02f, 0.001f) * 0.4f + 0.2f);
|
|
|
|
|
tileWeight *= lumaWeight;
|
|
|
|
|
vec2 sampleExposure = vec2(tileExposure * tileWeight, tileWeight);
|
|
|
|
|
sampleExposure = subgroupInclusiveAdd(sampleExposure);
|
|
|
|
|
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleExposure;
|
|
|
|
|
barrier();
|
|
|
|
|
for (uint i = 0; i < loopLength; i++) {
|
|
|
|
|
if ((gl_SubgroupID & (1u << i)) > 0u) {
|
|
|
|
|
sampleExposure += prefixSumCache[(gl_SubgroupID >> i << i) - 1u];
|
|
|
|
|
if (gl_SubgroupInvocationID == gl_SubgroupSize - 1u) prefixSumCache[gl_SubgroupID] = sampleExposure;
|
|
|
|
|
}
|
|
|
|
|
barrier();
|
|
|
|
|
}
|
|
|
|
|
if (gl_LocalInvocationIndex == 511u) {
|
|
|
|
|
float avgExposure = max(sampleExposure.x / sampleExposure.y * 29.3f, 1.0E-10f);
|
|
|
|
|
avgExposure = log2(avgExposure);
|
|
|
|
|
float prevAvgExposure = log2(texelFetch(pixelData2D, ivec2(0, 0), 0).x);
|
|
|
|
|
float frameTimeFixed = frameTime + step(frameCounter, 20) * 100.0f;
|
|
|
|
|
float exposureTime = clamp(frameTimeFixed * (2.0f / 1.0f), 0.0f, 1.0f);
|
|
|
|
|
avgExposure = mix(prevAvgExposure, avgExposure, exposureTime);
|
|
|
|
|
avgExposure = max(exp2(avgExposure), 1.0E-5f);
|
|
|
|
|
float exposure = GetExposureValue(avgExposure);
|
|
|
|
|
imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)glsl");
|
|
|
|
|
|
|
|
|
|
if (!FindUniqueTokenSequence(tokens, expectedMain, match.mainBegin, match.mainEnd) ||
|
|
|
|
|
CountTokenSequence(tokens,
|
|
|
|
|
{"layout", "(", "local_size_x", "=", "32", ",", "local_size_y", "=", "16",
|
|
|
|
|
")", "in", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"shared", "vec2", "prefixSumCache", "[", "32", "]", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens,
|
|
|
|
|
{"float", "GetExposureValue", "(", "float", "luminance", ")", "{"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"uniform", "int", "frameCounter", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"uniform", "float", "frameTime", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"uniform", "float", "aspectRatio", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"uniform", "vec2", "pixelSize", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"uniform", "sampler2D", "colortex2", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens, {"uniform", "sampler2D", "pixelData2D", ";"}) != 1 ||
|
|
|
|
|
CountTokenSequence(tokens,
|
|
|
|
|
{"layout", "(", "rg16f", ")", "uniform", "image2D", "img_pixelData2D", ";"}) !=
|
|
|
|
|
1) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No second user of the scratch array or lane-width-sensitive builtin may survive the
|
|
|
|
|
// rewrite. These counts describe the fully matched main body plus its one declaration.
|
|
|
|
|
if (CountToken(tokens, "prefixSumCache") != 9 || CountToken(tokens, "GetExposureValue") != 2 ||
|
|
|
|
|
CountToken(tokens, "subgroupInclusiveAdd") != 2 ||
|
|
|
|
|
CountToken(tokens, "gl_SubgroupInvocationID") != 4 || CountToken(tokens, "gl_SubgroupSize") != 4 ||
|
|
|
|
|
CountToken(tokens, "gl_SubgroupID") != 8 || CountToken(tokens, "gl_NumSubgroups") != 2 ||
|
|
|
|
|
CountToken(tokens, "gl_LocalInvocationIndex") != 2 || CountToken(tokens, "barrier") != 5 ||
|
|
|
|
|
CountToken(tokens, "findMSB") != 1 ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "subgroup", {"subgroupInclusiveAdd"}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(
|
|
|
|
|
tokens, "gl_Subgroup",
|
|
|
|
|
{"gl_SubgroupInvocationID", "gl_SubgroupSize", "gl_SubgroupID", "gl_NumSubgroups"}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SubGroup", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Warp", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_Thread", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "gl_SMID", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "ballot", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "shuffle", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "readInvocation", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "readFirstInvocation", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "anyInvocation", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "allInvocations", {}) ||
|
|
|
|
|
HasIdentifierWithPrefixOutsideAllowed(tokens, "mglExposure", {})) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String BuildWeightedExposureReductionReplacement() {
|
|
|
|
|
return R"glsl(void main() {
|
|
|
|
|
if (gl_LocalInvocationIndex != 0u) return;
|
|
|
|
|
float mglExposureAverage = 0.0f;
|
|
|
|
|
for (uint mglExposureY = 0u; mglExposureY < 16u; ++mglExposureY) {
|
|
|
|
|
for (uint mglExposureX = 0u; mglExposureX < 32u; ++mglExposureX) {
|
|
|
|
|
vec2 mglExposureTexCoord = (vec2(mglExposureX, mglExposureY) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f);
|
|
|
|
|
vec2 mglExposureSampleCoord = mglExposureTexCoord * (1.0f / 64.0f);
|
|
|
|
|
mglExposureSampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f;
|
|
|
|
|
mglExposureAverage += dot(textureLod(colortex2, mglExposureSampleCoord, 0.0f).rgb,
|
|
|
|
|
vec3(0.2125f, 0.7154f, 0.0721f));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mglExposureAverage /= 512.0f;
|
|
|
|
|
vec2 mglExposureWeightedSum = vec2(0.0f);
|
|
|
|
|
for (uint mglExposureY = 0u; mglExposureY < 16u; ++mglExposureY) {
|
|
|
|
|
for (uint mglExposureX = 0u; mglExposureX < 32u; ++mglExposureX) {
|
|
|
|
|
vec2 mglExposureTexCoord = (vec2(mglExposureX, mglExposureY) + 0.5f) * vec2(1.0f / 32.0f, 1.0f / 16.0f);
|
|
|
|
|
vec2 mglExposureSampleCoord = mglExposureTexCoord * (1.0f / 64.0f);
|
|
|
|
|
mglExposureSampleCoord.x += (15.0f / 32.0f) + pixelSize.x * 12.0f;
|
|
|
|
|
float mglExposureTile = dot(textureLod(colortex2, mglExposureSampleCoord, 0.0f).rgb,
|
|
|
|
|
vec3(0.2125f, 0.7154f, 0.0721f));
|
|
|
|
|
vec2 mglExposureDistance = mglExposureTexCoord * 2.0f - 1.0f;
|
|
|
|
|
mglExposureDistance.y /= aspectRatio;
|
|
|
|
|
float mglExposureWeight = remapSaturate(length(mglExposureDistance), 0.6f, 0.4f);
|
|
|
|
|
mglExposureTile = max(7.0E-7f, mglExposureTile);
|
|
|
|
|
float mglExposureLumaWeight = mglExposureAverage / mglExposureTile;
|
|
|
|
|
mglExposureLumaWeight = pow(mglExposureLumaWeight,
|
|
|
|
|
remapSaturate(mglExposureAverage, 0.02f, 0.001f) * 0.4f + 0.2f);
|
|
|
|
|
mglExposureWeight *= mglExposureLumaWeight;
|
|
|
|
|
mglExposureWeightedSum += vec2(mglExposureTile * mglExposureWeight, mglExposureWeight);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
float avgExposure = max(mglExposureWeightedSum.x / mglExposureWeightedSum.y * 29.3f, 1.0E-10f);
|
|
|
|
|
avgExposure = log2(avgExposure);
|
|
|
|
|
float prevAvgExposure = log2(texelFetch(pixelData2D, ivec2(0, 0), 0).x);
|
|
|
|
|
float frameTimeFixed = frameTime + step(frameCounter, 20) * 100.0f;
|
|
|
|
|
float exposureTime = clamp(frameTimeFixed * (2.0f / 1.0f), 0.0f, 1.0f);
|
|
|
|
|
avgExposure = mix(prevAvgExposure, avgExposure, exposureTime);
|
|
|
|
|
avgExposure = max(exp2(avgExposure), 1.0E-5f);
|
|
|
|
|
float exposure = GetExposureValue(avgExposure);
|
|
|
|
|
imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f));
|
|
|
|
|
})glsl";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SkipDirectiveWhitespace(const MobileGL::String& source, SizeT& pos, SizeT lineEnd) {
|
|
|
|
|
while (pos < lineEnd && std::isspace(static_cast<unsigned char>(source[pos]))) {
|
|
|
|
|
pos++;
|
|
|
|
@@ -1452,142 +929,6 @@ imageStore(img_pixelData2D, ivec2(0, 0), vec4(avgExposure, exposure, 0.0f, 0.0f)
|
|
|
|
|
namespace MobileGL {
|
|
|
|
|
namespace MG_Util {
|
|
|
|
|
namespace ShaderTranspiler {
|
|
|
|
|
Bool RewriteLinearSubgroupPrefixScanForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize,
|
|
|
|
|
String& source) {
|
|
|
|
|
constexpr Uint32 capturedSubgroupSize = 32;
|
|
|
|
|
const Bool narrowSubgroup = nativeSubgroupSize != 0u && nativeSubgroupSize < 16u &&
|
|
|
|
|
capturedSubgroupSize % nativeSubgroupSize == 0u;
|
|
|
|
|
const Bool wideSubgroup = nativeSubgroupSize > capturedSubgroupSize &&
|
|
|
|
|
nativeSubgroupSize % capturedSubgroupSize == 0u;
|
|
|
|
|
if (stage != ShaderStage::Compute || !IsPowerOfTwo(nativeSubgroupSize) ||
|
|
|
|
|
(!narrowSubgroup && !wideSubgroup)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Vector<CodeToken> tokens = TokenizeCode(source);
|
|
|
|
|
LinearPrefixScanMatch match;
|
|
|
|
|
if (!ParseLinearPrefixScanTemplate(tokens, match)) {
|
|
|
|
|
// Diagnosability: when the trigger op is present but the template no longer
|
|
|
|
|
// matches (e.g. the pack shipped a new shader revision), the affected device
|
|
|
|
|
// silently falls back to the driver's miscompiled path. Make that visible.
|
|
|
|
|
if (CountToken(tokens, "subgroupInclusiveAdd") > 0) {
|
|
|
|
|
MGLOG_W_ONCE("%s: subgroupInclusiveAdd present but the linear prefix-scan template "
|
|
|
|
|
"did not match; the subgroup-compatibility rewrite was NOT applied",
|
|
|
|
|
__func__);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const String replacement = BuildLinearPrefixScanReplacement(match);
|
|
|
|
|
source.replace(match.scanBegin, match.scanEnd - match.scanBegin, replacement);
|
|
|
|
|
// The declaration occurs before the replaced scan, so its original offsets remain
|
|
|
|
|
// valid after the first replacement.
|
|
|
|
|
source.replace(match.sharedArraySizeBegin, match.sharedArraySizeEnd - match.sharedArraySizeBegin,
|
|
|
|
|
"1024");
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Bool RewriteWeightedExposureSubgroupReductionForVulkan(ShaderStage stage, Uint32 nativeSubgroupSize,
|
|
|
|
|
String& source) {
|
|
|
|
|
if (stage != ShaderStage::Compute || !IsPowerOfTwo(nativeSubgroupSize) ||
|
|
|
|
|
nativeSubgroupSize >= 16u) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const Vector<CodeToken> tokens = TokenizeCode(source);
|
|
|
|
|
WeightedExposureReductionMatch match;
|
|
|
|
|
if (!ParseWeightedExposureReductionTemplate(tokens, match)) {
|
|
|
|
|
if (CountToken(tokens, "subgroupInclusiveAdd") == 2 &&
|
|
|
|
|
CountToken(tokens, "GetExposureValue") > 0) {
|
|
|
|
|
MGLOG_W_ONCE("%s: weighted exposure subgroup reductions were present but the complete "
|
|
|
|
|
"template did not match; the narrow-subgroup rewrite was NOT applied",
|
|
|
|
|
__func__);
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
source.replace(match.mainBegin, match.mainEnd - match.mainBegin,
|
|
|
|
|
BuildWeightedExposureReductionReplacement());
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
|
struct ShaderSourceQuirkContext {
|
|
|
|
|
ShaderStage stage = ShaderStage::Unknown;
|
|
|
|
|
BackendType backend = BackendType::Unknown;
|
|
|
|
|
MG_Backend::GpuVendorKind vendor = MG_Backend::GpuVendorKind::Unknown;
|
|
|
|
|
Uint32 subgroupSize = 0;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Device-quirk registry. Every entry is a narrowly scoped source rewrite that
|
|
|
|
|
// works around a specific driver defect. A quirk runs when its env override
|
|
|
|
|
// forces it on, or when the override is Auto and DeviceApplies matches the
|
|
|
|
|
// detected device. ForceOn bypasses only the device gate - each Apply keeps
|
|
|
|
|
// its own structural safety checks. Add new per-device workarounds here
|
|
|
|
|
// instead of open-coding them in PreprocessShaderSource.
|
|
|
|
|
struct ShaderSourceQuirk {
|
|
|
|
|
const char* name;
|
|
|
|
|
// Reads the override out of the captured env, never out of the live
|
|
|
|
|
// MG_Config table: a worker must see the same config the GL thread saw.
|
|
|
|
|
MG_Config::QuirkOverride (*GetOverride)(const CompileEnv&);
|
|
|
|
|
Bool (*DeviceApplies)(const ShaderSourceQuirkContext&);
|
|
|
|
|
Bool (*Apply)(const ShaderSourceQuirkContext&, String&);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
constexpr ShaderSourceQuirk kShaderSourceQuirks[] = {
|
|
|
|
|
{
|
|
|
|
|
// MOBILEGL_QUIRK_SUBGROUP_PREFIX_SCAN
|
|
|
|
|
"subgroup-prefix-scan-rewrite",
|
|
|
|
|
[](const CompileEnv& env) { return env.subgroupPrefixScanQuirk; },
|
|
|
|
|
[](const ShaderSourceQuirkContext& ctx) {
|
|
|
|
|
// Narrow subgroups overflow the pack's fixed subgroup-result scratch
|
|
|
|
|
// arrays. Qualcomm also miscompiles the recognized float InclusiveScan
|
|
|
|
|
// pattern when its native subgroup is wider than the captured 32 lanes.
|
|
|
|
|
return ctx.backend == BackendType::DirectVulkan &&
|
|
|
|
|
((ctx.subgroupSize != 0u && ctx.subgroupSize < 16u) ||
|
|
|
|
|
(ctx.vendor == MG_Backend::GpuVendorKind::Qualcomm &&
|
|
|
|
|
ctx.subgroupSize > 32u));
|
|
|
|
|
},
|
|
|
|
|
[](const ShaderSourceQuirkContext& ctx, String& source) {
|
|
|
|
|
const Bool exposureRewritten = RewriteWeightedExposureSubgroupReductionForVulkan(
|
|
|
|
|
ctx.stage, ctx.subgroupSize, source);
|
|
|
|
|
const Bool prefixScanRewritten = RewriteLinearSubgroupPrefixScanForVulkan(
|
|
|
|
|
ctx.stage, ctx.subgroupSize, source);
|
|
|
|
|
return exposureRewritten || prefixScanRewritten;
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
void ApplyShaderSourceQuirks(const CompileEnv& env, ShaderStage stage, String& source) {
|
|
|
|
|
// No backend at capture time means no device to match a quirk against,
|
|
|
|
|
// and (as before) no quirk can fire - not even a forced one, because
|
|
|
|
|
// every Apply reads device parameters that do not exist yet.
|
|
|
|
|
if (!env.HasBackend()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const ShaderSourceQuirkContext quirkContext{
|
|
|
|
|
stage,
|
|
|
|
|
env.backend,
|
|
|
|
|
env.params.GpuVendor,
|
|
|
|
|
env.params.SubgroupSize,
|
|
|
|
|
};
|
|
|
|
|
for (const ShaderSourceQuirk& quirk : kShaderSourceQuirks) {
|
|
|
|
|
const MG_Config::QuirkOverride quirkOverride = quirk.GetOverride(env);
|
|
|
|
|
if (quirkOverride == MG_Config::QuirkOverride::ForceOff) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (quirkOverride == MG_Config::QuirkOverride::Auto &&
|
|
|
|
|
!quirk.DeviceApplies(quirkContext)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (quirk.Apply(quirkContext, source)) {
|
|
|
|
|
MGLOG_D("ApplyShaderSourceQuirks: applied '%s'%s", quirk.name,
|
|
|
|
|
quirkOverride == MG_Config::QuirkOverride::ForceOn ? " (forced on)" : "");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
|
|
void PreprocessShaderSource(ShaderStage stage, String& source) {
|
|
|
|
|
PreprocessShaderSource(stage, source, *GetCurrentCompileEnv());
|
|
|
|
|
}
|
|
|
|
@@ -1627,7 +968,6 @@ namespace MobileGL {
|
|
|
|
|
ModernizeLegacyGLSL(stage, source, afterVersion);
|
|
|
|
|
InjectDepthRangeBuiltinShim(stage, source, afterVersion);
|
|
|
|
|
|
|
|
|
|
ApplyShaderSourceQuirks(env, stage, source);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Bool RetargetLegacyVersionDirectiveTo460(String& source) {
|
|
|
|
|