mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Fix] (MG_Backend/DirectVulkan): fixing Photon v1.1
- Flatten DailyWeatherVariation interface varyings - Correct internal-format component counts - Preserve GL draw-buffer slot semantics in render pass creation - relax GL_NONE / vec4-to-RGB pipeline checks
This commit is contained in:
@@ -51,6 +51,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool isMember = false;
|
||||
};
|
||||
|
||||
ShaderStage PickClipFixupStage(const Vector<SharedPtr<ShaderObject>>& shaders);
|
||||
|
||||
Bool IsVec4Float32(spvtools::opt::IRContext* context, Uint32 typeId, Uint32* outFloatTypeId) {
|
||||
auto* vecInst = context->get_def_use_mgr()->GetDef(typeId);
|
||||
if (!vecInst || vecInst->opcode() != spv::Op::OpTypeVector) return false;
|
||||
@@ -65,6 +67,588 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
return true;
|
||||
}
|
||||
|
||||
spvc_basetype MapReflectInterfaceToSpvcBasetype(const SpvReflectInterfaceVariable& variable) {
|
||||
if (variable.type_description == nullptr) {
|
||||
return SPVC_BASETYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
const auto flags = variable.type_description->type_flags;
|
||||
const auto width = variable.numeric.scalar.width;
|
||||
const auto signedness = variable.numeric.scalar.signedness;
|
||||
if ((flags & SPV_REFLECT_TYPE_FLAG_FLOAT) != 0) {
|
||||
switch (width) {
|
||||
case 16: return SPVC_BASETYPE_FP16;
|
||||
case 32: return SPVC_BASETYPE_FP32;
|
||||
case 64: return SPVC_BASETYPE_FP64;
|
||||
default: return SPVC_BASETYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
if ((flags & SPV_REFLECT_TYPE_FLAG_INT) != 0) {
|
||||
if (signedness != 0) {
|
||||
switch (width) {
|
||||
case 8: return SPVC_BASETYPE_INT8;
|
||||
case 16: return SPVC_BASETYPE_INT16;
|
||||
case 32: return SPVC_BASETYPE_INT32;
|
||||
case 64: return SPVC_BASETYPE_INT64;
|
||||
default: return SPVC_BASETYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
switch (width) {
|
||||
case 8: return SPVC_BASETYPE_UINT8;
|
||||
case 16: return SPVC_BASETYPE_UINT16;
|
||||
case 32: return SPVC_BASETYPE_UINT32;
|
||||
case 64: return SPVC_BASETYPE_UINT64;
|
||||
default: return SPVC_BASETYPE_UNKNOWN;
|
||||
}
|
||||
}
|
||||
if ((flags & SPV_REFLECT_TYPE_FLAG_BOOL) != 0) {
|
||||
return SPVC_BASETYPE_BOOLEAN;
|
||||
}
|
||||
return SPVC_BASETYPE_UNKNOWN;
|
||||
}
|
||||
|
||||
Uint32 GetReflectInterfaceLocationSpan(const SpvReflectInterfaceVariable& variable) {
|
||||
Uint32 locationSpan = variable.numeric.matrix.column_count;
|
||||
if (locationSpan == 0) {
|
||||
locationSpan = 1;
|
||||
}
|
||||
|
||||
for (Uint32 dimIndex = 0; dimIndex < variable.array.dims_count; ++dimIndex) {
|
||||
const Uint32 dim = variable.array.dims[dimIndex];
|
||||
if (dim == 0 || dim == SPV_REFLECT_ARRAY_DIM_RUNTIME) {
|
||||
continue;
|
||||
}
|
||||
locationSpan *= dim;
|
||||
}
|
||||
|
||||
return locationSpan;
|
||||
}
|
||||
|
||||
GLenum GetReflectInterfaceLocationType(const SpvReflectInterfaceVariable& variable) {
|
||||
MG_Util::ShaderTranspiler::SpvcType spvcType{};
|
||||
spvcType.basetype = MapReflectInterfaceToSpvcBasetype(variable);
|
||||
spvcType.vectorSize = variable.numeric.vector.component_count;
|
||||
if (spvcType.vectorSize == 0) {
|
||||
spvcType.vectorSize = variable.numeric.matrix.row_count;
|
||||
}
|
||||
if (spvcType.vectorSize == 0) {
|
||||
spvcType.vectorSize = 1;
|
||||
}
|
||||
spvcType.matCol = 1;
|
||||
|
||||
if (spvcType.vectorSize < 1 || spvcType.vectorSize > 4) {
|
||||
return GL_FALSE;
|
||||
}
|
||||
|
||||
switch (spvcType.basetype) {
|
||||
case SPVC_BASETYPE_BOOLEAN:
|
||||
switch (spvcType.vectorSize) {
|
||||
case 1: return GL_BOOL;
|
||||
case 2: return GL_BOOL_VEC2;
|
||||
case 3: return GL_BOOL_VEC3;
|
||||
case 4: return GL_BOOL_VEC4;
|
||||
default: return GL_FALSE;
|
||||
}
|
||||
case SPVC_BASETYPE_INT32:
|
||||
switch (spvcType.vectorSize) {
|
||||
case 1: return GL_INT;
|
||||
case 2: return GL_INT_VEC2;
|
||||
case 3: return GL_INT_VEC3;
|
||||
case 4: return GL_INT_VEC4;
|
||||
default: return GL_FALSE;
|
||||
}
|
||||
case SPVC_BASETYPE_UINT32:
|
||||
switch (spvcType.vectorSize) {
|
||||
case 1: return GL_UNSIGNED_INT;
|
||||
case 2: return GL_UNSIGNED_INT_VEC2;
|
||||
case 3: return GL_UNSIGNED_INT_VEC3;
|
||||
case 4: return GL_UNSIGNED_INT_VEC4;
|
||||
default: return GL_FALSE;
|
||||
}
|
||||
case SPVC_BASETYPE_FP32:
|
||||
switch (spvcType.vectorSize) {
|
||||
case 1: return GL_FLOAT;
|
||||
case 2: return GL_FLOAT_VEC2;
|
||||
case 3: return GL_FLOAT_VEC3;
|
||||
case 4: return GL_FLOAT_VEC4;
|
||||
default: return GL_FALSE;
|
||||
}
|
||||
case SPVC_BASETYPE_FP64:
|
||||
switch (spvcType.vectorSize) {
|
||||
case 1: return GL_DOUBLE;
|
||||
case 2: return GL_DOUBLE_VEC2;
|
||||
case 3: return GL_DOUBLE_VEC3;
|
||||
case 4: return GL_DOUBLE_VEC4;
|
||||
default: return GL_FALSE;
|
||||
}
|
||||
default:
|
||||
return GL_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
Uint32 GetReflectInterfaceLocationSignature(const SpvReflectInterfaceVariable& variable) {
|
||||
Uint32 vectorSize = variable.numeric.vector.component_count;
|
||||
if (vectorSize == 0) {
|
||||
vectorSize = variable.numeric.matrix.row_count;
|
||||
}
|
||||
if (vectorSize == 0) {
|
||||
vectorSize = 1;
|
||||
}
|
||||
if (vectorSize < 1 || vectorSize > 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Uint32 typeClass = 0;
|
||||
Uint32 scalarWidth = 0;
|
||||
switch (MapReflectInterfaceToSpvcBasetype(variable)) {
|
||||
case SPVC_BASETYPE_BOOLEAN:
|
||||
typeClass = 1;
|
||||
scalarWidth = 1;
|
||||
break;
|
||||
case SPVC_BASETYPE_INT8:
|
||||
typeClass = 2;
|
||||
scalarWidth = 8;
|
||||
break;
|
||||
case SPVC_BASETYPE_INT16:
|
||||
typeClass = 2;
|
||||
scalarWidth = 16;
|
||||
break;
|
||||
case SPVC_BASETYPE_INT32:
|
||||
typeClass = 2;
|
||||
scalarWidth = 32;
|
||||
break;
|
||||
case SPVC_BASETYPE_INT64:
|
||||
typeClass = 2;
|
||||
scalarWidth = 64;
|
||||
break;
|
||||
case SPVC_BASETYPE_UINT8:
|
||||
typeClass = 3;
|
||||
scalarWidth = 8;
|
||||
break;
|
||||
case SPVC_BASETYPE_UINT16:
|
||||
typeClass = 3;
|
||||
scalarWidth = 16;
|
||||
break;
|
||||
case SPVC_BASETYPE_UINT32:
|
||||
typeClass = 3;
|
||||
scalarWidth = 32;
|
||||
break;
|
||||
case SPVC_BASETYPE_UINT64:
|
||||
typeClass = 3;
|
||||
scalarWidth = 64;
|
||||
break;
|
||||
case SPVC_BASETYPE_FP16:
|
||||
typeClass = 4;
|
||||
scalarWidth = 16;
|
||||
break;
|
||||
case SPVC_BASETYPE_FP32:
|
||||
typeClass = 4;
|
||||
scalarWidth = 32;
|
||||
break;
|
||||
case SPVC_BASETYPE_FP64:
|
||||
typeClass = 4;
|
||||
scalarWidth = 64;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return (typeClass << 24) | (scalarWidth << 8) | vectorSize;
|
||||
}
|
||||
|
||||
Uint32 GetReflectInterfaceVectorSize(const SpvReflectInterfaceVariable& variable) {
|
||||
Uint32 vectorSize = variable.numeric.vector.component_count;
|
||||
if (vectorSize == 0) {
|
||||
vectorSize = variable.numeric.matrix.row_count;
|
||||
}
|
||||
if (vectorSize == 0) {
|
||||
vectorSize = 1;
|
||||
}
|
||||
return vectorSize;
|
||||
}
|
||||
|
||||
struct StageInterfaceCursor {
|
||||
Uint32 location = 0;
|
||||
Uint32 component = 0;
|
||||
};
|
||||
|
||||
struct StageInterfaceSummary {
|
||||
static constexpr Uint32 kMaxComponentSlots = ProgramFactory::VkProgramObject::kMaxVertexInputLocations * 4;
|
||||
|
||||
Array<Uint32, kMaxComponentSlots> slotSignatures{};
|
||||
Array<String, kMaxComponentSlots> slotDebugNames{};
|
||||
};
|
||||
|
||||
Uint32 CountOccupiedStageInterfaceSlots(const StageInterfaceSummary& summary) {
|
||||
Uint32 occupiedSlotCount = 0;
|
||||
for (Uint32 slotIndex = 0; slotIndex < StageInterfaceSummary::kMaxComponentSlots; ++slotIndex) {
|
||||
if (summary.slotSignatures[slotIndex] != 0) {
|
||||
++occupiedSlotCount;
|
||||
}
|
||||
}
|
||||
return occupiedSlotCount;
|
||||
}
|
||||
|
||||
void ValidateTransformedSpirv(const Vector<Uint>& spirv, ShaderStage shaderStage, Uint programExternalIndex) {
|
||||
if (spirv.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
spv_const_binary_t binary = {spirv.data(), spirv.size()};
|
||||
spv_target_env targetEnv = SPV_ENV_VULKAN_1_0;
|
||||
if (spirv.size() > 1) {
|
||||
const Uint32 versionWord = spirv[1];
|
||||
const Uint32 major = (versionWord >> 16) & 0xffu;
|
||||
const Uint32 minor = (versionWord >> 8) & 0xffu;
|
||||
if (major > 1 || (major == 1 && minor >= 6)) {
|
||||
targetEnv = SPV_ENV_VULKAN_1_3;
|
||||
} else if (major == 1 && minor >= 5) {
|
||||
targetEnv = SPV_ENV_VULKAN_1_2;
|
||||
} else if (major == 1 && minor >= 4) {
|
||||
targetEnv = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
|
||||
} else if (major == 1 && minor >= 3) {
|
||||
targetEnv = SPV_ENV_VULKAN_1_1;
|
||||
}
|
||||
}
|
||||
|
||||
spv_context context = spvContextCreate(targetEnv);
|
||||
MOBILEGL_ASSERT(context != nullptr,
|
||||
"ProgramFactory::ValidateTransformedSpirv: failed to create validator context for stage=%d program=%u",
|
||||
static_cast<Int>(shaderStage),
|
||||
programExternalIndex);
|
||||
|
||||
spv_validator_options options = spvValidatorOptionsCreate();
|
||||
MOBILEGL_ASSERT(options != nullptr,
|
||||
"ProgramFactory::ValidateTransformedSpirv: failed to create validator options for stage=%d program=%u",
|
||||
static_cast<Int>(shaderStage),
|
||||
programExternalIndex);
|
||||
spvValidatorOptionsSetFriendlyNames(options, true);
|
||||
|
||||
spv_diagnostic diagnostic = nullptr;
|
||||
const spv_result_t result = spvValidateWithOptions(context, options, &binary, &diagnostic);
|
||||
MOBILEGL_ASSERT(
|
||||
result == SPV_SUCCESS,
|
||||
"ProgramFactory::ValidateTransformedSpirv: validation failed for stage=%d program=%u result=%d line=%zu column=%zu index=%zu msg=%s",
|
||||
static_cast<Int>(shaderStage),
|
||||
programExternalIndex,
|
||||
static_cast<Int>(result),
|
||||
diagnostic != nullptr ? diagnostic->position.line : 0,
|
||||
diagnostic != nullptr ? diagnostic->position.column : 0,
|
||||
diagnostic != nullptr ? diagnostic->position.index : 0,
|
||||
diagnostic != nullptr && diagnostic->error != nullptr ? diagnostic->error : "<null>");
|
||||
|
||||
spvDiagnosticDestroy(diagnostic);
|
||||
spvValidatorOptionsDestroy(options);
|
||||
spvContextDestroy(context);
|
||||
}
|
||||
|
||||
void ReflectStageInterfaceVariable(const SpvReflectInterfaceVariable& variable,
|
||||
Bool reflectInputs,
|
||||
StageInterfaceSummary& outSummary,
|
||||
Uint programExternalIndex,
|
||||
const char* stageLabel,
|
||||
StageInterfaceCursor& cursor,
|
||||
Uint32 locationBase = 0,
|
||||
Bool allowImplicitPacking = false,
|
||||
const char* inheritedName = nullptr) {
|
||||
if ((variable.decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* debugName = variable.name;
|
||||
if (debugName == nullptr || debugName[0] == '\0') {
|
||||
debugName = inheritedName;
|
||||
}
|
||||
if (debugName == nullptr || debugName[0] == '\0') {
|
||||
debugName = "<null>";
|
||||
}
|
||||
|
||||
const Bool hasConcreteLocation =
|
||||
allowImplicitPacking ? (variable.location != 0 || variable.component != 0)
|
||||
: variable.location != std::numeric_limits<Uint32>::max();
|
||||
const Bool hasConcreteComponent =
|
||||
allowImplicitPacking ? (variable.component != 0)
|
||||
: variable.component != std::numeric_limits<Uint32>::max();
|
||||
const Uint32 explicitLocationBase = locationBase + (hasConcreteLocation ? variable.location : 0u);
|
||||
|
||||
if (variable.member_count > 0 && variable.members != nullptr) {
|
||||
StageInterfaceCursor memberCursor = cursor;
|
||||
if (hasConcreteLocation) {
|
||||
memberCursor.location = explicitLocationBase;
|
||||
memberCursor.component = 0;
|
||||
}
|
||||
for (Uint32 memberIndex = 0; memberIndex < variable.member_count; ++memberIndex) {
|
||||
ReflectStageInterfaceVariable(variable.members[memberIndex], reflectInputs, outSummary,
|
||||
programExternalIndex, stageLabel, memberCursor,
|
||||
explicitLocationBase, true, debugName);
|
||||
}
|
||||
if (memberCursor.location > cursor.location ||
|
||||
(memberCursor.location == cursor.location && memberCursor.component > cursor.component)) {
|
||||
cursor = memberCursor;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(
|
||||
hasConcreteLocation || allowImplicitPacking || locationBase != 0,
|
||||
"ProgramFactory::ReflectStageInterface: missing concrete %s %s location for name='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
|
||||
const Uint32 component = variable.component;
|
||||
MOBILEGL_ASSERT(
|
||||
component < 4 || component == std::numeric_limits<Uint32>::max(),
|
||||
"ProgramFactory::ReflectStageInterface: unsupported %s %s component=%u at location=%u name='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
component,
|
||||
explicitLocationBase,
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
|
||||
const Uint32 locationSignature = GetReflectInterfaceLocationSignature(variable);
|
||||
MOBILEGL_ASSERT(
|
||||
locationSignature != 0,
|
||||
"ProgramFactory::ReflectStageInterface: unsupported %s %s type at location=%u name='%s' flags=0x%x width=%u signed=%u vec=%u rows=%u cols=%u program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
explicitLocationBase,
|
||||
debugName,
|
||||
static_cast<Uint32>(variable.type_description != nullptr ? variable.type_description->type_flags : 0),
|
||||
variable.numeric.scalar.width,
|
||||
variable.numeric.scalar.signedness,
|
||||
variable.numeric.vector.component_count,
|
||||
variable.numeric.matrix.row_count,
|
||||
variable.numeric.matrix.column_count,
|
||||
programExternalIndex);
|
||||
|
||||
const Uint32 vectorSize = GetReflectInterfaceVectorSize(variable);
|
||||
const Uint32 locationSpan = GetReflectInterfaceLocationSpan(variable);
|
||||
Uint32 startLocation = explicitLocationBase;
|
||||
Uint32 startComponent = hasConcreteComponent ? component : 0u;
|
||||
const Bool useImplicitPacking = allowImplicitPacking && !hasConcreteLocation && !hasConcreteComponent;
|
||||
if (useImplicitPacking) {
|
||||
startLocation = cursor.location;
|
||||
startComponent = cursor.component;
|
||||
if (locationSpan > 1 || startComponent + vectorSize > 4) {
|
||||
if (startComponent != 0) {
|
||||
++startLocation;
|
||||
startComponent = 0;
|
||||
}
|
||||
if (locationSpan == 1 && startComponent + vectorSize > 4) {
|
||||
++startLocation;
|
||||
startComponent = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(
|
||||
startComponent < 4,
|
||||
"ProgramFactory::ReflectStageInterface: %s %s component overflow at location=%u component=%u name='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
startLocation,
|
||||
startComponent,
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
MOBILEGL_ASSERT(
|
||||
locationSpan == 1 || startComponent == 0,
|
||||
"ProgramFactory::ReflectStageInterface: %s %s multi-location variable starts at non-zero component location=%u component=%u name='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
startLocation,
|
||||
startComponent,
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
|
||||
for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) {
|
||||
const Uint32 expandedLocation = startLocation + locationOffset;
|
||||
const Uint32 componentBase = (locationOffset == 0) ? startComponent : 0u;
|
||||
MOBILEGL_ASSERT(
|
||||
expandedLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations,
|
||||
"ProgramFactory::ReflectStageInterface: %s %s location=%u span=%u exceeds tracked limit for name='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
startLocation,
|
||||
locationSpan,
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
MOBILEGL_ASSERT(
|
||||
componentBase + vectorSize <= 4,
|
||||
"ProgramFactory::ReflectStageInterface: %s %s component span overflow at location=%u component=%u vec=%u name='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
expandedLocation,
|
||||
componentBase,
|
||||
vectorSize,
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
for (Uint32 componentOffset = 0; componentOffset < vectorSize; ++componentOffset) {
|
||||
const Uint32 expandedComponent = componentBase + componentOffset;
|
||||
const Uint32 slotIndex = expandedLocation * 4 + expandedComponent;
|
||||
MOBILEGL_ASSERT(
|
||||
outSummary.slotSignatures[slotIndex] == 0 || outSummary.slotSignatures[slotIndex] == locationSignature,
|
||||
"ProgramFactory::ReflectStageInterface: conflicting %s %s type at location=%u component=%u existingSignature=0x%x existingName='%s' newSignature=0x%x newName='%s' program=%u",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
expandedLocation,
|
||||
expandedComponent,
|
||||
outSummary.slotSignatures[slotIndex],
|
||||
outSummary.slotDebugNames[slotIndex].empty() ? "<null>" : outSummary.slotDebugNames[slotIndex].c_str(),
|
||||
locationSignature,
|
||||
debugName,
|
||||
programExternalIndex);
|
||||
outSummary.slotSignatures[slotIndex] = locationSignature;
|
||||
outSummary.slotDebugNames[slotIndex] = debugName;
|
||||
}
|
||||
}
|
||||
|
||||
StageInterfaceCursor endCursor{};
|
||||
if (locationSpan > 1) {
|
||||
endCursor.location = startLocation + locationSpan;
|
||||
endCursor.component = 0;
|
||||
} else {
|
||||
endCursor.location = startLocation;
|
||||
endCursor.component = startComponent + vectorSize;
|
||||
if (endCursor.component >= 4) {
|
||||
endCursor.location += endCursor.component / 4;
|
||||
endCursor.component %= 4;
|
||||
}
|
||||
}
|
||||
if (endCursor.location > cursor.location ||
|
||||
(endCursor.location == cursor.location && endCursor.component > cursor.component)) {
|
||||
cursor = endCursor;
|
||||
}
|
||||
}
|
||||
|
||||
void ReflectStageInterface(ShaderStage targetStage,
|
||||
Bool reflectInputs,
|
||||
const Vector<SharedPtr<ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
StageInterfaceSummary& outSummary,
|
||||
Uint programExternalIndex,
|
||||
const char* stageLabel) {
|
||||
outSummary.slotSignatures.fill(0);
|
||||
|
||||
for (SizeT moduleIndex = 0; moduleIndex < shaders.size() && moduleIndex < spirv.size(); ++moduleIndex) {
|
||||
if (!shaders[moduleIndex] || shaders[moduleIndex]->GetShaderStage() != targetStage) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& module = spirv[moduleIndex];
|
||||
if (module.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SpvReflectShaderModule reflectModule{};
|
||||
const SpvReflectResult createResult =
|
||||
spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule);
|
||||
MOBILEGL_ASSERT(
|
||||
createResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectStageInterface: failed to create reflection module for %s %s (result=%d program=%u)",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
static_cast<Int>(createResult),
|
||||
programExternalIndex);
|
||||
if (createResult != SPV_REFLECT_RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t variableCount = 0;
|
||||
SpvReflectResult reflectResult = reflectInputs
|
||||
? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, nullptr)
|
||||
: spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, nullptr);
|
||||
MOBILEGL_ASSERT(
|
||||
reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectStageInterface: failed to enumerate %s %s variables (result=%d program=%u)",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
static_cast<Int>(reflectResult),
|
||||
programExternalIndex);
|
||||
|
||||
Vector<SpvReflectInterfaceVariable*> variables(variableCount);
|
||||
if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && variableCount > 0) {
|
||||
reflectResult = reflectInputs
|
||||
? spvReflectEnumerateInputVariables(&reflectModule, &variableCount, variables.data())
|
||||
: spvReflectEnumerateOutputVariables(&reflectModule, &variableCount, variables.data());
|
||||
MOBILEGL_ASSERT(
|
||||
reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectStageInterface: failed to fetch %s %s variables (result=%d program=%u)",
|
||||
stageLabel,
|
||||
reflectInputs ? "input" : "output",
|
||||
static_cast<Int>(reflectResult),
|
||||
programExternalIndex);
|
||||
}
|
||||
|
||||
if (reflectResult == SPV_REFLECT_RESULT_SUCCESS) {
|
||||
StageInterfaceCursor stageCursor{};
|
||||
for (auto* variable : variables) {
|
||||
if (variable == nullptr) {
|
||||
continue;
|
||||
}
|
||||
ReflectStageInterfaceVariable(*variable, reflectInputs, outSummary, programExternalIndex,
|
||||
stageLabel, stageCursor);
|
||||
}
|
||||
}
|
||||
|
||||
spvReflectDestroyShaderModule(&reflectModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ValidateRasterizationStageInterface(const Vector<SharedPtr<ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
ProgramFactory::VkProgramObject& entry,
|
||||
Uint programExternalIndex) {
|
||||
const ShaderStage producerStage = PickClipFixupStage(shaders);
|
||||
entry.rasterizationProducerStage = producerStage;
|
||||
entry.producerOutputComponentCount = 0;
|
||||
entry.fragmentInputComponentCount = 0;
|
||||
if (producerStage == ShaderStage::Unknown) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bool hasFragmentStage = false;
|
||||
for (const auto& shader : shaders) {
|
||||
if (shader && shader->GetShaderStage() == ShaderStage::Fragment) {
|
||||
hasFragmentStage = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasFragmentStage) {
|
||||
return;
|
||||
}
|
||||
|
||||
StageInterfaceSummary producerOutputs{};
|
||||
StageInterfaceSummary fragmentInputs{};
|
||||
ReflectStageInterface(producerStage, false, shaders, spirv, producerOutputs, programExternalIndex,
|
||||
"producer");
|
||||
ReflectStageInterface(ShaderStage::Fragment, true, shaders, spirv, fragmentInputs, programExternalIndex,
|
||||
"fragment");
|
||||
entry.producerOutputComponentCount = CountOccupiedStageInterfaceSlots(producerOutputs);
|
||||
entry.fragmentInputComponentCount = CountOccupiedStageInterfaceSlots(fragmentInputs);
|
||||
|
||||
for (Uint32 slotIndex = 0; slotIndex < StageInterfaceSummary::kMaxComponentSlots; ++slotIndex) {
|
||||
if (fragmentInputs.slotSignatures[slotIndex] == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(
|
||||
producerOutputs.slotSignatures[slotIndex] == fragmentInputs.slotSignatures[slotIndex],
|
||||
"ProgramFactory::ValidateRasterizationStageInterface: location=%u component=%u producerSignature=0x%x producerName='%s' fragmentSignature=0x%x fragmentName='%s' program=%u",
|
||||
slotIndex / 4,
|
||||
slotIndex % 4,
|
||||
producerOutputs.slotSignatures[slotIndex],
|
||||
producerOutputs.slotDebugNames[slotIndex].empty() ? "<null>" : producerOutputs.slotDebugNames[slotIndex].c_str(),
|
||||
fragmentInputs.slotSignatures[slotIndex],
|
||||
fragmentInputs.slotDebugNames[slotIndex].empty() ? "<null>" : fragmentInputs.slotDebugNames[slotIndex].c_str(),
|
||||
programExternalIndex);
|
||||
}
|
||||
}
|
||||
|
||||
Bool ResolveDirectPositionTarget(spvtools::opt::IRContext* context, Uint32 variableId,
|
||||
PositionTargetInfo* outTarget) {
|
||||
auto* varInst = context->get_def_use_mgr()->GetDef(variableId);
|
||||
@@ -620,6 +1204,142 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
void ProgramFactory::ReflectVertexInputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const {
|
||||
entry.activeVertexInputLocationMask = 0;
|
||||
entry.vertexInputTypes.fill(0);
|
||||
|
||||
for (SizeT moduleIndex = 0; moduleIndex < shaders.size() && moduleIndex < spirv.size(); ++moduleIndex) {
|
||||
if (!shaders[moduleIndex] || shaders[moduleIndex]->GetShaderStage() != ShaderStage::Vertex) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& module = spirv[moduleIndex];
|
||||
if (module.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SpvReflectShaderModule reflectModule{};
|
||||
const SpvReflectResult createResult =
|
||||
spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule);
|
||||
MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectVertexInputs: failed to create reflection module (result=%d)",
|
||||
static_cast<Int>(createResult));
|
||||
if (createResult != SPV_REFLECT_RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t inputCount = 0;
|
||||
SpvReflectResult reflectResult = spvReflectEnumerateInputVariables(&reflectModule, &inputCount, nullptr);
|
||||
MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectVertexInputs: failed to enumerate input variables (result=%d)",
|
||||
static_cast<Int>(reflectResult));
|
||||
Vector<SpvReflectInterfaceVariable*> inputs(inputCount);
|
||||
if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && inputCount > 0) {
|
||||
reflectResult = spvReflectEnumerateInputVariables(&reflectModule, &inputCount, inputs.data());
|
||||
MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectVertexInputs: failed to fetch input variables (result=%d)",
|
||||
static_cast<Int>(reflectResult));
|
||||
}
|
||||
|
||||
if (reflectResult == SPV_REFLECT_RESULT_SUCCESS) {
|
||||
for (auto* input : inputs) {
|
||||
if (input == nullptr || (input->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GLenum locationType = GetReflectInterfaceLocationType(*input);
|
||||
MOBILEGL_ASSERT(locationType != GL_FALSE,
|
||||
"ProgramFactory::ReflectVertexInputs: unsupported vertex input type at location=%u name='%s'",
|
||||
input->location,
|
||||
input->name ? input->name : "<null>");
|
||||
const Uint32 locationSpan = GetReflectInterfaceLocationSpan(*input);
|
||||
for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) {
|
||||
const Uint32 expandedLocation = input->location + locationOffset;
|
||||
if (expandedLocation >= VkProgramObject::kMaxVertexInputLocations) {
|
||||
break;
|
||||
}
|
||||
|
||||
entry.activeVertexInputLocationMask |= (1u << expandedLocation);
|
||||
entry.vertexInputTypes[expandedLocation] = locationType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spvReflectDestroyShaderModule(&reflectModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProgramFactory::ReflectFragmentOutputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const {
|
||||
entry.activeFragmentOutputLocationMask = 0;
|
||||
entry.fragmentOutputTypes.fill(0);
|
||||
|
||||
for (SizeT moduleIndex = 0; moduleIndex < shaders.size() && moduleIndex < spirv.size(); ++moduleIndex) {
|
||||
if (!shaders[moduleIndex] || shaders[moduleIndex]->GetShaderStage() != ShaderStage::Fragment) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& module = spirv[moduleIndex];
|
||||
if (module.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
SpvReflectShaderModule reflectModule{};
|
||||
const SpvReflectResult createResult =
|
||||
spvReflectCreateShaderModule(module.size() * sizeof(Uint), module.data(), &reflectModule);
|
||||
MOBILEGL_ASSERT(createResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectFragmentOutputs: failed to create reflection module (result=%d)",
|
||||
static_cast<Int>(createResult));
|
||||
if (createResult != SPV_REFLECT_RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint32_t outputCount = 0;
|
||||
SpvReflectResult reflectResult = spvReflectEnumerateOutputVariables(&reflectModule, &outputCount, nullptr);
|
||||
MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectFragmentOutputs: failed to enumerate output variables (result=%d)",
|
||||
static_cast<Int>(reflectResult));
|
||||
Vector<SpvReflectInterfaceVariable*> outputs(outputCount);
|
||||
if (reflectResult == SPV_REFLECT_RESULT_SUCCESS && outputCount > 0) {
|
||||
reflectResult = spvReflectEnumerateOutputVariables(&reflectModule, &outputCount, outputs.data());
|
||||
MOBILEGL_ASSERT(reflectResult == SPV_REFLECT_RESULT_SUCCESS,
|
||||
"ProgramFactory::ReflectFragmentOutputs: failed to fetch output variables (result=%d)",
|
||||
static_cast<Int>(reflectResult));
|
||||
}
|
||||
|
||||
if (reflectResult == SPV_REFLECT_RESULT_SUCCESS) {
|
||||
for (auto* output : outputs) {
|
||||
if (output == nullptr || (output->decoration_flags & SPV_REFLECT_DECORATION_BUILT_IN) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GLenum locationType = GetReflectInterfaceLocationType(*output);
|
||||
MOBILEGL_ASSERT(locationType != GL_FALSE,
|
||||
"ProgramFactory::ReflectFragmentOutputs: unsupported fragment output type at location=%u name='%s'",
|
||||
output->location,
|
||||
output->name ? output->name : "<null>");
|
||||
const Uint32 locationSpan = GetReflectInterfaceLocationSpan(*output);
|
||||
for (Uint32 locationOffset = 0; locationOffset < locationSpan; ++locationOffset) {
|
||||
const Uint32 expandedLocation = output->location + locationOffset;
|
||||
if (expandedLocation >= VkProgramObject::kMaxVertexInputLocations) {
|
||||
break;
|
||||
}
|
||||
|
||||
entry.activeFragmentOutputLocationMask |= (1u << expandedLocation);
|
||||
entry.fragmentOutputTypes[expandedLocation] = locationType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
spvReflectDestroyShaderModule(&reflectModule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program,
|
||||
const Vector<Vector<Uint>>& spirv, VkProgramObject& entry) const {
|
||||
// Initialize layout vectors
|
||||
@@ -829,6 +1549,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
auto& moduleSpv = moduleSpirvs[i];
|
||||
if (moduleSpv.empty()) continue;
|
||||
|
||||
ValidateTransformedSpirv(moduleSpv, shaders[i]->GetShaderStage(), program.GetExternalIndex());
|
||||
|
||||
VkShaderModuleCreateInfo smci{VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
|
||||
smci.codeSize = moduleSpv.size() * sizeof(Uint);
|
||||
smci.pCode = moduleSpv.data();
|
||||
@@ -847,6 +1569,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
// Reflect and create layout as part of the program object
|
||||
ValidateRasterizationStageInterface(shaders, moduleSpirvs, entry, program.GetExternalIndex());
|
||||
ReflectVertexInputs(shaders, moduleSpirvs, entry);
|
||||
ReflectFragmentOutputs(shaders, moduleSpirvs, entry);
|
||||
ReflectLayout(program, moduleSpirvs, entry);
|
||||
|
||||
return entry;
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
using HashType = Uint64;
|
||||
|
||||
struct VkProgramObject {
|
||||
static constexpr Uint32 kMaxVertexInputLocations = 32;
|
||||
|
||||
HashType hash = 0;
|
||||
Vector<VkPipelineShaderStageCreateInfo> stages;
|
||||
Vector<VkShaderModule> modules;
|
||||
@@ -50,6 +52,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<Int> samplerUniformLocationByBinding;
|
||||
Vector<TextureTarget> samplerTextureTargetByBinding;
|
||||
Int globalUboBinding = -1;
|
||||
Uint32 activeVertexInputLocationMask = 0;
|
||||
Array<GLenum, kMaxVertexInputLocations> vertexInputTypes{};
|
||||
Uint32 activeFragmentOutputLocationMask = 0;
|
||||
Array<GLenum, kMaxVertexInputLocations> fragmentOutputTypes{};
|
||||
ShaderStage rasterizationProducerStage = ShaderStage::Unknown;
|
||||
Uint32 producerOutputComponentCount = 0;
|
||||
Uint32 fragmentInputComponentCount = 0;
|
||||
|
||||
static inline VkDevice s_device = VK_NULL_HANDLE;
|
||||
|
||||
@@ -69,10 +78,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
||||
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
|
||||
globalUboBinding = other.globalUboBinding;
|
||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||
vertexInputTypes = other.vertexInputTypes;
|
||||
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||
other.hash = 0;
|
||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.globalUboBinding = -1;
|
||||
other.activeVertexInputLocationMask = 0;
|
||||
other.activeFragmentOutputLocationMask = 0;
|
||||
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||
other.producerOutputComponentCount = 0;
|
||||
other.fragmentInputComponentCount = 0;
|
||||
}
|
||||
VkProgramObject& operator=(VkProgramObject&& other) noexcept {
|
||||
if (this == &other) {
|
||||
@@ -91,10 +112,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
samplerUniformLocationByBinding = std::move(other.samplerUniformLocationByBinding);
|
||||
samplerTextureTargetByBinding = std::move(other.samplerTextureTargetByBinding);
|
||||
globalUboBinding = other.globalUboBinding;
|
||||
activeVertexInputLocationMask = other.activeVertexInputLocationMask;
|
||||
vertexInputTypes = other.vertexInputTypes;
|
||||
activeFragmentOutputLocationMask = other.activeFragmentOutputLocationMask;
|
||||
fragmentOutputTypes = other.fragmentOutputTypes;
|
||||
rasterizationProducerStage = other.rasterizationProducerStage;
|
||||
producerOutputComponentCount = other.producerOutputComponentCount;
|
||||
fragmentInputComponentCount = other.fragmentInputComponentCount;
|
||||
other.hash = 0;
|
||||
other.descriptorSetLayout = VK_NULL_HANDLE;
|
||||
other.pipelineLayout = VK_NULL_HANDLE;
|
||||
other.globalUboBinding = -1;
|
||||
other.activeVertexInputLocationMask = 0;
|
||||
other.activeFragmentOutputLocationMask = 0;
|
||||
other.rasterizationProducerStage = ShaderStage::Unknown;
|
||||
other.producerOutputComponentCount = 0;
|
||||
other.fragmentInputComponentCount = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -139,6 +172,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
|
||||
private:
|
||||
static TextureTarget UniformTypeToTextureTarget(GLenum glType);
|
||||
void ReflectVertexInputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
void ReflectFragmentOutputs(const Vector<SharedPtr<MG_State::GLState::ShaderObject>>& shaders,
|
||||
const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector<Vector<Uint>>& spirv,
|
||||
VkProgramObject& entry) const;
|
||||
|
||||
|
||||
@@ -170,34 +170,30 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Bool isDefaultFbo = (&fbo == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get());
|
||||
// Color attachment
|
||||
auto& drawbufs = fbo.GetDrawBuffers();
|
||||
Int validDrawBufCount = 0;
|
||||
for (Int i = 0; i < drawbufs.size(); ++i) {
|
||||
auto drawbuf = drawbufs[i];
|
||||
if (drawbuf != FramebufferAttachmentType::None)
|
||||
validDrawBufCount = std::max(validDrawBufCount, i + 1);
|
||||
}
|
||||
const Uint32 colorAttachmentSlotCount = static_cast<Uint32>(drawbufs.size());
|
||||
|
||||
Int width = 0;
|
||||
Int height = 0;
|
||||
Vector<VkAttachmentDescription> attachmentDescriptions;
|
||||
attachmentDescriptions.reserve(validDrawBufCount + 1);
|
||||
Vector<VkAttachmentReference> colorAttachmentRefs(validDrawBufCount);
|
||||
attachmentDescriptions.reserve(colorAttachmentSlotCount + 1);
|
||||
// Keep the full GL draw buffer slot span so fragment outputs targeting GL_NONE map to VK_ATTACHMENT_UNUSED.
|
||||
Vector<VkAttachmentReference> colorAttachmentRefs(colorAttachmentSlotCount);
|
||||
for (auto& attachmentRef : colorAttachmentRefs) {
|
||||
attachmentRef.attachment = VK_ATTACHMENT_UNUSED;
|
||||
attachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||||
}
|
||||
Vector<PendingClearAttachmentInfo> pendingClearAttachments;
|
||||
pendingClearAttachments.reserve(validDrawBufCount + 1);
|
||||
pendingClearAttachments.reserve(colorAttachmentSlotCount + 1);
|
||||
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
|
||||
trackedAttachmentLayouts.reserve(validDrawBufCount + 1);
|
||||
trackedAttachmentLayouts.reserve(colorAttachmentSlotCount + 1);
|
||||
auto& textureResources = RenderPassEntry::s_textureResourcesScratch;
|
||||
textureResources.clear();
|
||||
textureResources.reserve(validDrawBufCount + 1);
|
||||
textureResources.reserve(colorAttachmentSlotCount + 1);
|
||||
Vector<VkImageView> attachmentViews;
|
||||
attachmentViews.reserve(validDrawBufCount + 1);
|
||||
attachmentViews.reserve(colorAttachmentSlotCount + 1);
|
||||
// This should automatically work on default & offscreen FBO
|
||||
// assuming default FBO has the right param
|
||||
for (Int i = 0; i < validDrawBufCount; ++i) {
|
||||
for (Uint32 i = 0; i < colorAttachmentSlotCount; ++i) {
|
||||
auto drawbuf = drawbufs[i];
|
||||
if (drawbuf == FramebufferAttachmentType::None ||
|
||||
fbo.GetAttachment(drawbuf).IsRenderbuffer())
|
||||
@@ -385,6 +381,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
attachmentDescriptions.emplace_back(depthAttachmentDescription);
|
||||
depthAttachmentRef.attachment = depthAttachmentIndex;
|
||||
}
|
||||
const Bool hasDepthStencilAttachment = depthAttachmentRef.attachment != VK_ATTACHMENT_UNUSED;
|
||||
|
||||
// Subpass
|
||||
VkSubpassDescription subpassDesc;
|
||||
@@ -437,6 +434,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Move(trackedAttachmentLayouts),
|
||||
static_cast<Uint32>(attachmentViews.size()),
|
||||
static_cast<Uint32>(colorAttachmentRefs.size()),
|
||||
hasDepthStencilAttachment,
|
||||
extent,
|
||||
1 };
|
||||
MGLOG_D("VkRenderPassManager::GetOrCreateRenderPass: hash=0x%llx compatibilityHash=0x%llx attachmentCount=%u colorAttachmentCount=%u extent=%dx%d",
|
||||
|
||||
@@ -48,6 +48,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
|
||||
Uint32 attachmentCount = 0;
|
||||
Uint32 colorAttachmentCount = 0;
|
||||
Bool hasDepthStencilAttachment = false;
|
||||
IntVec2 extent = {0, 0};
|
||||
Uint32 subpass = 0;
|
||||
|
||||
@@ -62,6 +63,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
std::swap(trackedAttachmentLayouts, that.trackedAttachmentLayouts);
|
||||
std::swap(attachmentCount, that.attachmentCount);
|
||||
std::swap(colorAttachmentCount, that.colorAttachmentCount);
|
||||
std::swap(hasDepthStencilAttachment, that.hasDepthStencilAttachment);
|
||||
std::swap(extent, that.extent);
|
||||
std::swap(subpass, that.subpass);
|
||||
}
|
||||
@@ -74,6 +76,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const Vector<TrackedAttachmentLayoutInfo>& trackedAttachmentLayouts,
|
||||
Uint32 attachmentCount,
|
||||
Uint32 colorAttachmentCount,
|
||||
Bool hasDepthStencilAttachment,
|
||||
IntVec2 extent, int subpass):
|
||||
hash(hash),
|
||||
renderPass(renderpass),
|
||||
@@ -83,6 +86,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
trackedAttachmentLayouts(Move(trackedAttachmentLayouts)),
|
||||
attachmentCount(attachmentCount),
|
||||
colorAttachmentCount(colorAttachmentCount),
|
||||
hasDepthStencilAttachment(hasDepthStencilAttachment),
|
||||
extent(extent),
|
||||
subpass(subpass)
|
||||
{}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h"
|
||||
#include "MG_Util/Converters/GLToMG/TextureEnumConverter.h"
|
||||
#include "MG_Util/Converters/MGToVk/RenderStateEnumConverter.h"
|
||||
#include "MG_Util/Metrics/TextureMetrics.h"
|
||||
#include <vulkan/vulkan_core.h>
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
@@ -57,12 +58,411 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
static VkColorComponentFlags GetSupportedColorWriteMaskForComponentCount(SizeT componentCount) {
|
||||
switch (componentCount) {
|
||||
case 1:
|
||||
return VK_COLOR_COMPONENT_R_BIT;
|
||||
case 2:
|
||||
return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT;
|
||||
case 3:
|
||||
return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT;
|
||||
case 4:
|
||||
return VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
default:
|
||||
MOBILEGL_ASSERT(false,
|
||||
"GetSupportedColorWriteMaskForComponentCount: unsupported componentCount=%zu",
|
||||
componentCount);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
enum class NumericDomain {
|
||||
Unknown,
|
||||
FloatLike,
|
||||
Sint,
|
||||
Uint,
|
||||
};
|
||||
|
||||
static NumericDomain GetNumericDomainForShaderValueType(GLenum glType) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT:
|
||||
case GL_FLOAT_VEC2:
|
||||
case GL_FLOAT_VEC3:
|
||||
case GL_FLOAT_VEC4:
|
||||
return NumericDomain::FloatLike;
|
||||
case GL_INT:
|
||||
case GL_INT_VEC2:
|
||||
case GL_INT_VEC3:
|
||||
case GL_INT_VEC4:
|
||||
return NumericDomain::Sint;
|
||||
case GL_UNSIGNED_INT:
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
return NumericDomain::Uint;
|
||||
default:
|
||||
return NumericDomain::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static SizeT GetComponentCountForShaderValueType(GLenum glType) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT:
|
||||
case GL_INT:
|
||||
case GL_UNSIGNED_INT:
|
||||
return 1;
|
||||
case GL_FLOAT_VEC2:
|
||||
case GL_INT_VEC2:
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
return 2;
|
||||
case GL_FLOAT_VEC3:
|
||||
case GL_INT_VEC3:
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
return 3;
|
||||
case GL_FLOAT_VEC4:
|
||||
case GL_INT_VEC4:
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
return 4;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static NumericDomain GetNumericDomainForVertexFormat(VkFormat format) {
|
||||
switch (format) {
|
||||
case VK_FORMAT_R32_SFLOAT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
case VK_FORMAT_R32G32B32_SFLOAT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
case VK_FORMAT_R16_SNORM:
|
||||
case VK_FORMAT_R16G16_SNORM:
|
||||
case VK_FORMAT_R16G16B16_SNORM:
|
||||
case VK_FORMAT_R16G16B16A16_SNORM:
|
||||
case VK_FORMAT_R16_UNORM:
|
||||
case VK_FORMAT_R16G16_UNORM:
|
||||
case VK_FORMAT_R16G16B16_UNORM:
|
||||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||||
case VK_FORMAT_R16_SSCALED:
|
||||
case VK_FORMAT_R16G16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16_SSCALED:
|
||||
case VK_FORMAT_R16G16B16A16_SSCALED:
|
||||
case VK_FORMAT_R16_USCALED:
|
||||
case VK_FORMAT_R16G16_USCALED:
|
||||
case VK_FORMAT_R16G16B16_USCALED:
|
||||
case VK_FORMAT_R16G16B16A16_USCALED:
|
||||
case VK_FORMAT_R8_SNORM:
|
||||
case VK_FORMAT_R8G8_SNORM:
|
||||
case VK_FORMAT_R8G8B8_SNORM:
|
||||
case VK_FORMAT_R8G8B8A8_SNORM:
|
||||
case VK_FORMAT_R8_UNORM:
|
||||
case VK_FORMAT_R8G8_UNORM:
|
||||
case VK_FORMAT_R8G8B8_UNORM:
|
||||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||||
case VK_FORMAT_R8_SSCALED:
|
||||
case VK_FORMAT_R8G8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8_SSCALED:
|
||||
case VK_FORMAT_R8G8B8A8_SSCALED:
|
||||
case VK_FORMAT_R8_USCALED:
|
||||
case VK_FORMAT_R8G8_USCALED:
|
||||
case VK_FORMAT_R8G8B8_USCALED:
|
||||
case VK_FORMAT_R8G8B8A8_USCALED:
|
||||
return NumericDomain::FloatLike;
|
||||
case VK_FORMAT_R32_SINT:
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
case VK_FORMAT_R32G32B32_SINT:
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
case VK_FORMAT_R16_SINT:
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
case VK_FORMAT_R16G16B16_SINT:
|
||||
case VK_FORMAT_R16G16B16A16_SINT:
|
||||
case VK_FORMAT_R8_SINT:
|
||||
case VK_FORMAT_R8G8_SINT:
|
||||
case VK_FORMAT_R8G8B8_SINT:
|
||||
case VK_FORMAT_R8G8B8A8_SINT:
|
||||
return NumericDomain::Sint;
|
||||
case VK_FORMAT_R32_UINT:
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
case VK_FORMAT_R32G32B32_UINT:
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
case VK_FORMAT_R16_UINT:
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
case VK_FORMAT_R16G16B16_UINT:
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
case VK_FORMAT_R8_UINT:
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
case VK_FORMAT_R8G8B8_UINT:
|
||||
case VK_FORMAT_R8G8B8A8_UINT:
|
||||
return NumericDomain::Uint;
|
||||
default:
|
||||
return NumericDomain::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool TryCoerceVertexFormatNumericDomain(VkFormat sourceFormat,
|
||||
NumericDomain targetDomain,
|
||||
VkFormat& outFormat) {
|
||||
const NumericDomain sourceDomain = GetNumericDomainForVertexFormat(sourceFormat);
|
||||
if (sourceDomain == targetDomain || targetDomain == NumericDomain::Unknown) {
|
||||
outFormat = sourceFormat;
|
||||
return true;
|
||||
}
|
||||
if (sourceDomain == NumericDomain::FloatLike || targetDomain == NumericDomain::FloatLike) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (sourceFormat) {
|
||||
case VK_FORMAT_R32_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32G32_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32G32_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32G32B32_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32G32B32_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32G32B32A32_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R32G32B32A32_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32G32_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32G32_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32G32B32_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32G32B32_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R32G32B32A32_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R32G32B32A32_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16G16_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16G16B16_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16G16B16A16_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R16G16B16A16_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16G16_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16G16B16_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R16G16B16A16_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R16G16B16A16_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8G8_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8G8_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8G8B8_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8G8B8_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8G8B8A8_SINT:
|
||||
outFormat = targetDomain == NumericDomain::Uint ? VK_FORMAT_R8G8B8A8_UINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8G8_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8G8_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8G8B8_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8G8B8_SINT : sourceFormat;
|
||||
return true;
|
||||
case VK_FORMAT_R8G8B8A8_UINT:
|
||||
outFormat = targetDomain == NumericDomain::Sint ? VK_FORMAT_R8G8B8A8_SINT : sourceFormat;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static NumericDomain GetNumericDomainForTextureInternalFormat(TextureInternalFormat format) {
|
||||
switch (format) {
|
||||
case TextureInternalFormat::R8I:
|
||||
case TextureInternalFormat::R16I:
|
||||
case TextureInternalFormat::R32I:
|
||||
case TextureInternalFormat::RG8I:
|
||||
case TextureInternalFormat::RG16I:
|
||||
case TextureInternalFormat::RG32I:
|
||||
case TextureInternalFormat::RGB8I:
|
||||
case TextureInternalFormat::RGB16I:
|
||||
case TextureInternalFormat::RGB32I:
|
||||
case TextureInternalFormat::RGBA8I:
|
||||
case TextureInternalFormat::RGBA16I:
|
||||
case TextureInternalFormat::RGBA32I:
|
||||
return NumericDomain::Sint;
|
||||
case TextureInternalFormat::R8UI:
|
||||
case TextureInternalFormat::R16UI:
|
||||
case TextureInternalFormat::R32UI:
|
||||
case TextureInternalFormat::RG8UI:
|
||||
case TextureInternalFormat::RG16UI:
|
||||
case TextureInternalFormat::RG32UI:
|
||||
case TextureInternalFormat::RGB8UI:
|
||||
case TextureInternalFormat::RGB16UI:
|
||||
case TextureInternalFormat::RGB32UI:
|
||||
case TextureInternalFormat::RGBA8UI:
|
||||
case TextureInternalFormat::RGBA16UI:
|
||||
case TextureInternalFormat::RGBA32UI:
|
||||
case TextureInternalFormat::RGB10A2UI:
|
||||
return NumericDomain::Uint;
|
||||
case TextureInternalFormat::DepthComponent:
|
||||
case TextureInternalFormat::DepthComponent16:
|
||||
case TextureInternalFormat::DepthComponent24:
|
||||
case TextureInternalFormat::DepthComponent32:
|
||||
case TextureInternalFormat::DepthComponent32F:
|
||||
case TextureInternalFormat::Depth24Stencil8:
|
||||
case TextureInternalFormat::Depth32FStencil8:
|
||||
case TextureInternalFormat::DepthStencil:
|
||||
return NumericDomain::Unknown;
|
||||
default:
|
||||
return NumericDomain::FloatLike;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool HasTransientVertexIndexBufferThisFrame(
|
||||
const Vector<const MG_State::GLState::BufferObject*>& buffers,
|
||||
const MG_State::GLState::BufferObject* buffer) {
|
||||
return std::find(buffers.begin(), buffers.end(), buffer) != buffers.end();
|
||||
}
|
||||
|
||||
static Uint32 BuildVertexInputAttributeMask(const Vector<VkVertexInputAttributeDescription>& attributes) {
|
||||
Uint32 attributeMask = 0;
|
||||
for (const auto& attribute : attributes) {
|
||||
if (attribute.location < 32) {
|
||||
attributeMask |= (1u << attribute.location);
|
||||
}
|
||||
}
|
||||
return attributeMask;
|
||||
}
|
||||
|
||||
static Bool TryGetCurrentVertexAttributeFormat(GLenum glType, VkFormat& outFormat) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT:
|
||||
outFormat = VK_FORMAT_R32_SFLOAT;
|
||||
return true;
|
||||
case GL_FLOAT_VEC2:
|
||||
outFormat = VK_FORMAT_R32G32_SFLOAT;
|
||||
return true;
|
||||
case GL_FLOAT_VEC3:
|
||||
outFormat = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
return true;
|
||||
case GL_FLOAT_VEC4:
|
||||
outFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
return true;
|
||||
case GL_INT:
|
||||
outFormat = VK_FORMAT_R32_SINT;
|
||||
return true;
|
||||
case GL_INT_VEC2:
|
||||
outFormat = VK_FORMAT_R32G32_SINT;
|
||||
return true;
|
||||
case GL_INT_VEC3:
|
||||
outFormat = VK_FORMAT_R32G32B32_SINT;
|
||||
return true;
|
||||
case GL_INT_VEC4:
|
||||
outFormat = VK_FORMAT_R32G32B32A32_SINT;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT:
|
||||
outFormat = VK_FORMAT_R32_UINT;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
outFormat = VK_FORMAT_R32G32_UINT;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
outFormat = VK_FORMAT_R32G32B32_UINT;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
outFormat = VK_FORMAT_R32G32B32A32_UINT;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Bool TryGetCurrentVertexAttributeUploadPayload(
|
||||
const MG_State::GLState::CurrentVertexAttributeValue& currentValue,
|
||||
GLenum glType,
|
||||
VkFormat& outFormat,
|
||||
const void*& outData,
|
||||
VkDeviceSize& outSize) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT:
|
||||
outFormat = VK_FORMAT_R32_SFLOAT;
|
||||
outData = currentValue.floatValue.data();
|
||||
outSize = sizeof(Float);
|
||||
return true;
|
||||
case GL_FLOAT_VEC2:
|
||||
outFormat = VK_FORMAT_R32G32_SFLOAT;
|
||||
outData = currentValue.floatValue.data();
|
||||
outSize = sizeof(Float) * 2;
|
||||
return true;
|
||||
case GL_FLOAT_VEC3:
|
||||
outFormat = VK_FORMAT_R32G32B32_SFLOAT;
|
||||
outData = currentValue.floatValue.data();
|
||||
outSize = sizeof(Float) * 3;
|
||||
return true;
|
||||
case GL_FLOAT_VEC4:
|
||||
outFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
|
||||
outData = currentValue.floatValue.data();
|
||||
outSize = sizeof(Float) * 4;
|
||||
return true;
|
||||
case GL_INT:
|
||||
outFormat = VK_FORMAT_R32_SINT;
|
||||
outData = currentValue.intValue.data();
|
||||
outSize = sizeof(Int32);
|
||||
return true;
|
||||
case GL_INT_VEC2:
|
||||
outFormat = VK_FORMAT_R32G32_SINT;
|
||||
outData = currentValue.intValue.data();
|
||||
outSize = sizeof(Int32) * 2;
|
||||
return true;
|
||||
case GL_INT_VEC3:
|
||||
outFormat = VK_FORMAT_R32G32B32_SINT;
|
||||
outData = currentValue.intValue.data();
|
||||
outSize = sizeof(Int32) * 3;
|
||||
return true;
|
||||
case GL_INT_VEC4:
|
||||
outFormat = VK_FORMAT_R32G32B32A32_SINT;
|
||||
outData = currentValue.intValue.data();
|
||||
outSize = sizeof(Int32) * 4;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT:
|
||||
outFormat = VK_FORMAT_R32_UINT;
|
||||
outData = currentValue.uintValue.data();
|
||||
outSize = sizeof(Uint32);
|
||||
return true;
|
||||
case GL_UNSIGNED_INT_VEC2:
|
||||
outFormat = VK_FORMAT_R32G32_UINT;
|
||||
outData = currentValue.uintValue.data();
|
||||
outSize = sizeof(Uint32) * 2;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT_VEC3:
|
||||
outFormat = VK_FORMAT_R32G32B32_UINT;
|
||||
outData = currentValue.uintValue.data();
|
||||
outSize = sizeof(Uint32) * 3;
|
||||
return true;
|
||||
case GL_UNSIGNED_INT_VEC4:
|
||||
outFormat = VK_FORMAT_R32G32B32A32_UINT;
|
||||
outData = currentValue.uintValue.data();
|
||||
outSize = sizeof(Uint32) * 4;
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* VkImageLayoutToString(VkImageLayout layout) {
|
||||
switch (layout) {
|
||||
case VK_IMAGE_LAYOUT_UNDEFINED:
|
||||
@@ -890,8 +1290,14 @@ void main() {
|
||||
Bool VulkanRenderer::UploadAndBindVertexBuffers(
|
||||
VkCommandBuffer commandBuffer, const MG_State::GLState::VertexArrayObject& vao) {
|
||||
auto& vertexInputState = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
const auto& program = *MG_State::pGLContext->GetCurrentProgram();
|
||||
const auto transformFlags = GetShaderTransformFlags(m_swapchainObject.GetPreTransform());
|
||||
const auto& programObj = m_programFactory->GetOrCreateProgram(program, transformFlags);
|
||||
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
||||
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vertexInputState.attributes);
|
||||
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
|
||||
|
||||
const auto bindingCount = vertexInputState.bindings.size();
|
||||
const auto bindingCount = vertexInputState.bindings.size() + static_cast<SizeT>(std::popcount(missingAttribMask));
|
||||
|
||||
Vector<VkBuffer> vkBuffers(bindingCount, VK_NULL_HANDLE);
|
||||
Vector<VkDeviceSize> vkOffsets(bindingCount, 0);
|
||||
@@ -912,6 +1318,9 @@ void main() {
|
||||
};
|
||||
|
||||
for (SizeT binding = 0; binding < bindingCount; ++binding) {
|
||||
if (binding >= vertexInputState.bindings.size()) {
|
||||
break;
|
||||
}
|
||||
const SizeT bufferKey = vertexInputState.bindingBufferKeys[binding];
|
||||
const MG_State::GLState::BufferObject* sourceBuffer = findBufferByKey(bufferKey);
|
||||
MOBILEGL_ASSERT(sourceBuffer != nullptr, "UploadAndBindVertexStreams failed to resolve source buffer");
|
||||
@@ -946,6 +1355,37 @@ void main() {
|
||||
vkOffsets[binding] = slice.offset;
|
||||
}
|
||||
|
||||
SizeT syntheticBinding = vertexInputState.bindings.size();
|
||||
for (Uint32 location = 0; location < 32; ++location) {
|
||||
if ((missingAttribMask & (1u << location)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto glType = programObj.vertexInputTypes[location];
|
||||
const auto& currentValue = MG_State::pGLContext->GetCurrentVertexAttribute(location);
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
const void* sourceData = nullptr;
|
||||
VkDeviceSize sourceSize = 0;
|
||||
const Bool supported = TryGetCurrentVertexAttributeUploadPayload(currentValue, glType, format,
|
||||
sourceData, sourceSize);
|
||||
MOBILEGL_ASSERT(supported,
|
||||
"DirectVulkan does not support current generic vertex attribute type yet: program=%u location=%u type=0x%x",
|
||||
program.GetExternalIndex(), location, glType);
|
||||
|
||||
BufferSlice slice{};
|
||||
if (!m_bufferManager.UploadTransient(BufferKind::Vertex, m_frameContext.GetCurrentFrameIndex(),
|
||||
sourceData, sourceSize, 16, slice)) {
|
||||
MOBILEGL_ASSERT(false,
|
||||
"UploadAndBindVertexStreams skipped: failed to upload current attribute binding for location %u",
|
||||
location);
|
||||
return false;
|
||||
}
|
||||
|
||||
vkBuffers[syntheticBinding] = slice.buffer;
|
||||
vkOffsets[syntheticBinding] = slice.offset;
|
||||
++syntheticBinding;
|
||||
}
|
||||
|
||||
vkCmdBindVertexBuffers(commandBuffer, 0, static_cast<Uint32>(bindingCount), vkBuffers.data(), vkOffsets.data());
|
||||
return true;
|
||||
}
|
||||
@@ -1558,8 +1998,111 @@ void main() {
|
||||
return VK_NULL_HANDLE;
|
||||
}
|
||||
|
||||
const auto& limits = m_physicalDevice.properties.limits;
|
||||
if (programObj.fragmentInputComponentCount != 0) {
|
||||
MOBILEGL_ASSERT(
|
||||
programObj.fragmentInputComponentCount <= limits.maxFragmentInputComponents,
|
||||
"GetOrCreatePipeline: fragmentInputComponents=%u exceeds device limit=%u program=%u producerStage=%d",
|
||||
programObj.fragmentInputComponentCount,
|
||||
limits.maxFragmentInputComponents,
|
||||
program.GetExternalIndex(),
|
||||
static_cast<Int>(programObj.rasterizationProducerStage));
|
||||
}
|
||||
if (programObj.producerOutputComponentCount != 0) {
|
||||
Uint32 producerOutputLimit = 0;
|
||||
switch (programObj.rasterizationProducerStage) {
|
||||
case ShaderStage::Vertex:
|
||||
producerOutputLimit = limits.maxVertexOutputComponents;
|
||||
break;
|
||||
case ShaderStage::Geometry:
|
||||
producerOutputLimit = limits.maxGeometryOutputComponents;
|
||||
break;
|
||||
case ShaderStage::TessEval:
|
||||
producerOutputLimit = limits.maxTessellationEvaluationOutputComponents;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (producerOutputLimit != 0) {
|
||||
MOBILEGL_ASSERT(
|
||||
programObj.producerOutputComponentCount <= producerOutputLimit,
|
||||
"GetOrCreatePipeline: producerOutputComponents=%u exceeds stage limit=%u program=%u producerStage=%d",
|
||||
programObj.producerOutputComponentCount,
|
||||
producerOutputLimit,
|
||||
program.GetExternalIndex(),
|
||||
static_cast<Int>(programObj.rasterizationProducerStage));
|
||||
}
|
||||
}
|
||||
|
||||
auto vertexInputHash = m_vertexInputStateFactory->ComputeHash(vao);
|
||||
auto& vis = m_vertexInputStateFactory->GetOrCreateVertexInputState(vao);
|
||||
const Uint32 vertexInputAttribMask = BuildVertexInputAttributeMask(vis.attributes);
|
||||
const Uint32 activeAttribMask = programObj.activeVertexInputLocationMask;
|
||||
const Uint32 missingAttribMask = activeAttribMask & ~vertexInputAttribMask;
|
||||
Vector<VkVertexInputAttributeDescription> patchedAttributes = vis.attributes;
|
||||
Bool hasPatchedVertexAttributes = false;
|
||||
for (auto& attribute : patchedAttributes) {
|
||||
if (attribute.location >= 32 || (activeAttribMask & (1u << attribute.location)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GLenum shaderInputType = programObj.vertexInputTypes[attribute.location];
|
||||
const NumericDomain shaderInputDomain = GetNumericDomainForShaderValueType(shaderInputType);
|
||||
const NumericDomain vertexInputDomain = GetNumericDomainForVertexFormat(attribute.format);
|
||||
if (shaderInputDomain == NumericDomain::Unknown || vertexInputDomain == NumericDomain::Unknown ||
|
||||
shaderInputDomain == vertexInputDomain) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkFormat patchedFormat = VK_FORMAT_UNDEFINED;
|
||||
const Bool canPatch = TryCoerceVertexFormatNumericDomain(attribute.format, shaderInputDomain, patchedFormat);
|
||||
MOBILEGL_ASSERT(
|
||||
canPatch,
|
||||
"GetOrCreatePipeline: vertex input location=%u format=%d mismatches shader input type=%u program=%u",
|
||||
attribute.location,
|
||||
static_cast<Int>(attribute.format),
|
||||
static_cast<Uint32>(shaderInputType),
|
||||
program.GetExternalIndex());
|
||||
|
||||
MGLOG_W("GetOrCreatePipeline: patching vertex input location=%u format=%d -> %d to match shader input type=%u for program=%u",
|
||||
attribute.location,
|
||||
static_cast<Int>(attribute.format),
|
||||
static_cast<Int>(patchedFormat),
|
||||
static_cast<Uint32>(shaderInputType),
|
||||
program.GetExternalIndex());
|
||||
attribute.format = patchedFormat;
|
||||
hasPatchedVertexAttributes = true;
|
||||
}
|
||||
VertexInputStateBuilder syntheticVertexInputBuilder;
|
||||
const VkPipelineVertexInputStateCreateInfo* pipelineVertexInputState = &vis.state;
|
||||
if (missingAttribMask != 0 || hasPatchedVertexAttributes) {
|
||||
for (const auto& binding : vis.bindings) {
|
||||
syntheticVertexInputBuilder.AddBinding(binding.binding, binding.stride, binding.inputRate);
|
||||
}
|
||||
for (const auto& attribute : patchedAttributes) {
|
||||
syntheticVertexInputBuilder.AddAttribute(attribute.location, attribute.binding, attribute.format,
|
||||
attribute.offset);
|
||||
}
|
||||
|
||||
Uint32 syntheticBinding = static_cast<Uint32>(vis.bindings.size());
|
||||
for (Uint32 location = 0; location < 32; ++location) {
|
||||
if ((missingAttribMask & (1u << location)) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||||
const Bool supported = TryGetCurrentVertexAttributeFormat(programObj.vertexInputTypes[location], format);
|
||||
MOBILEGL_ASSERT(supported,
|
||||
"DirectVulkan does not support current generic vertex attribute type yet: program=%u location=%u type=0x%x activeAttribMask=0x%x vertexInputAttribMask=0x%x",
|
||||
program.GetExternalIndex(), location, programObj.vertexInputTypes[location],
|
||||
activeAttribMask, vertexInputAttribMask);
|
||||
|
||||
syntheticVertexInputBuilder.AddBinding(syntheticBinding, 0, VK_VERTEX_INPUT_RATE_VERTEX);
|
||||
syntheticVertexInputBuilder.AddAttribute(location, syntheticBinding, format, 0);
|
||||
++syntheticBinding;
|
||||
}
|
||||
pipelineVertexInputState = &syntheticVertexInputBuilder.Build();
|
||||
}
|
||||
auto cullFaceEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::CullFace);
|
||||
auto depthTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::DepthTest);
|
||||
auto mask = MG_State::pGLContext->GetColorMask();
|
||||
@@ -1585,24 +2128,164 @@ void main() {
|
||||
.depthWriteEnable = depthTestEnabled && MG_State::pGLContext->GetDepthMask(),
|
||||
.depthCompareOp = MG_Util::ConvertDepthTestFuncToVkEnum(MG_State::pGLContext->GetDepthFunc()),
|
||||
.stages = &programObj.stages,
|
||||
.vertexInputState = &vis.state
|
||||
.vertexInputState = pipelineVertexInputState
|
||||
};
|
||||
const Bool hasDepthStencilAttachment = renderPassEntry.hasDepthStencilAttachment;
|
||||
MOBILEGL_ASSERT(
|
||||
hasDepthStencilAttachment || (!payload.depthTestEnable && !payload.depthWriteEnable),
|
||||
"GetOrCreatePipeline: render pass has no depth attachment but depthTestEnable=%d depthWriteEnable=%d program=%u attachmentCount=%u colorAttachmentCount=%u",
|
||||
payload.depthTestEnable ? 1 : 0,
|
||||
payload.depthWriteEnable ? 1 : 0,
|
||||
program.GetExternalIndex(),
|
||||
renderPassEntry.attachmentCount,
|
||||
renderPassEntry.colorAttachmentCount);
|
||||
const Uint32 fragmentOutputMask = programObj.activeFragmentOutputLocationMask;
|
||||
MOBILEGL_ASSERT(
|
||||
(fragmentOutputMask >> payload.colorAttachmentCount) == 0,
|
||||
"GetOrCreatePipeline: fragmentOutputMask=0x%x exceeds colorAttachmentCount=%u for program=%u",
|
||||
fragmentOutputMask,
|
||||
payload.colorAttachmentCount,
|
||||
program.GetExternalIndex());
|
||||
MOBILEGL_ASSERT(payload.colorAttachmentCount <= PipelineFactory::PipelineCreatePayload::kMaxColorAttachments,
|
||||
"GetOrCreatePipeline: colorAttachmentCount=%u exceeds payload capacity",
|
||||
payload.colorAttachmentCount);
|
||||
const auto& drawFboBinding =
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
MOBILEGL_ASSERT(drawFboBinding != nullptr, "GetOrCreatePipeline: draw framebuffer is null");
|
||||
const Bool isDefaultDrawFbo =
|
||||
drawFboBinding.get() == MG_Impl::GLImpl::FramebufferImpl::pDefaultFramebufferInfo->defaultFBO.get();
|
||||
const auto& drawBuffers = drawFboBinding->GetDrawBuffers();
|
||||
for (Uint32 i = 0; i < payload.colorAttachmentCount; ++i) {
|
||||
BlendFactor srcRGB = BlendFactor::One;
|
||||
BlendFactor dstRGB = BlendFactor::Zero;
|
||||
BlendFactor srcAlpha = BlendFactor::One;
|
||||
BlendFactor dstAlpha = BlendFactor::Zero;
|
||||
MG_State::pGLContext->GetBlendFuncIndexed(i, srcRGB, dstRGB, srcAlpha, dstAlpha);
|
||||
const Bool blendEnabled = MG_State::pGLContext->IsCapabilityEnabledIndexed(CapabilityInput::Blend, i);
|
||||
VkColorComponentFlags attachmentColorWriteMask = colorWriteMask;
|
||||
Bool effectiveBlendEnabled = blendEnabled;
|
||||
if (!isDefaultDrawFbo && i < drawBuffers.size()) {
|
||||
const auto drawBuffer = drawBuffers[i];
|
||||
if (drawBuffer == FramebufferAttachmentType::None) {
|
||||
// GL ignores writes and per-target blend state for GL_NONE draw buffer slots.
|
||||
attachmentColorWriteMask = 0;
|
||||
effectiveBlendEnabled = false;
|
||||
}
|
||||
if (drawBuffer != FramebufferAttachmentType::None) {
|
||||
const auto& attachment = drawFboBinding->GetAttachment(drawBuffer);
|
||||
if (attachment.IsTexture()) {
|
||||
auto* texture = attachment.GetTexture().get();
|
||||
MOBILEGL_ASSERT(texture != nullptr,
|
||||
"GetOrCreatePipeline: color attachment %u texture is null",
|
||||
i);
|
||||
const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
|
||||
MOBILEGL_ASSERT(textureResource != nullptr,
|
||||
"GetOrCreatePipeline: failed to sync color attachment textureId=%d",
|
||||
texture->GetExternalIndex());
|
||||
VkFormatProperties attachmentFormatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(
|
||||
m_physicalDevice.handle,
|
||||
textureResource->format,
|
||||
&attachmentFormatProperties);
|
||||
MOBILEGL_ASSERT(
|
||||
(attachmentFormatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT) != 0,
|
||||
"GetOrCreatePipeline: color attachment %u format=%d textureId=%d lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT (program=%u)",
|
||||
i,
|
||||
static_cast<Int>(textureResource->format),
|
||||
texture->GetExternalIndex(),
|
||||
program.GetExternalIndex());
|
||||
const SizeT componentCount = MG_Util::GetBaseInternalFormatComponentCount(texture->GetFormat());
|
||||
const NumericDomain attachmentNumericDomain =
|
||||
GetNumericDomainForTextureInternalFormat(texture->GetFormat());
|
||||
for (Uint32 outputLocation = 0;
|
||||
outputLocation < ProgramFactory::VkProgramObject::kMaxVertexInputLocations;
|
||||
++outputLocation) {
|
||||
if ((programObj.activeFragmentOutputLocationMask & (1u << outputLocation)) == 0 ||
|
||||
outputLocation != i) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const GLenum fragmentOutputType = programObj.fragmentOutputTypes[outputLocation];
|
||||
const NumericDomain fragmentOutputDomain =
|
||||
GetNumericDomainForShaderValueType(fragmentOutputType);
|
||||
// GL allows fragment outputs with more components than the bound color attachment;
|
||||
// excess components are discarded during conversion to the attachment format.
|
||||
MOBILEGL_ASSERT(
|
||||
attachmentNumericDomain == NumericDomain::Unknown ||
|
||||
fragmentOutputDomain == NumericDomain::Unknown ||
|
||||
attachmentNumericDomain == fragmentOutputDomain,
|
||||
"GetOrCreatePipeline: fragment output location=%d type=%u mismatches color attachment %u internalFormat=%d textureId=%d program=%u",
|
||||
static_cast<Int>(outputLocation),
|
||||
static_cast<Uint32>(fragmentOutputType),
|
||||
i,
|
||||
static_cast<Int>(texture->GetFormat()),
|
||||
texture->GetExternalIndex(),
|
||||
program.GetExternalIndex());
|
||||
}
|
||||
const VkColorComponentFlags supportedColorWriteMask =
|
||||
GetSupportedColorWriteMaskForComponentCount(componentCount);
|
||||
if ((attachmentColorWriteMask & ~supportedColorWriteMask) != 0) {
|
||||
MGLOG_W(
|
||||
"GetOrCreatePipeline: clamping colorWriteMask=0x%x to 0x%x on color attachment %u (componentCount=%zu textureId=%d internalFormat=%d program=%u blendEnabled=%d)",
|
||||
static_cast<Uint32>(attachmentColorWriteMask),
|
||||
static_cast<Uint32>(attachmentColorWriteMask & supportedColorWriteMask),
|
||||
i,
|
||||
componentCount,
|
||||
texture->GetExternalIndex(),
|
||||
static_cast<Int>(texture->GetFormat()),
|
||||
program.GetExternalIndex(),
|
||||
effectiveBlendEnabled ? 1 : 0);
|
||||
attachmentColorWriteMask &= supportedColorWriteMask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (effectiveBlendEnabled) {
|
||||
MOBILEGL_ASSERT(i < drawBuffers.size(),
|
||||
"GetOrCreatePipeline: color attachment %u is out of draw buffer range %zu",
|
||||
i, drawBuffers.size());
|
||||
|
||||
VkFormat colorAttachmentFormat = VK_FORMAT_UNDEFINED;
|
||||
Int textureExternalIndex = -1;
|
||||
if (isDefaultDrawFbo) {
|
||||
colorAttachmentFormat = m_swapchainObject.GetSurfaceFormat().format;
|
||||
} else {
|
||||
const auto drawBuffer = drawBuffers[i];
|
||||
MOBILEGL_ASSERT(drawBuffer != FramebufferAttachmentType::None,
|
||||
"GetOrCreatePipeline: blend is enabled on draw buffer %u but the attachment is None",
|
||||
i);
|
||||
const auto& attachment = drawFboBinding->GetAttachment(drawBuffer);
|
||||
MOBILEGL_ASSERT(attachment.IsTexture(),
|
||||
"GetOrCreatePipeline: blend validation currently expects texture color attachments only");
|
||||
auto* texture = attachment.GetTexture().get();
|
||||
MOBILEGL_ASSERT(texture != nullptr,
|
||||
"GetOrCreatePipeline: color attachment %u texture is null",
|
||||
i);
|
||||
textureExternalIndex = texture->GetExternalIndex();
|
||||
const auto* textureResource = m_textureManager->SyncTextureAndGetDescriptor(*texture);
|
||||
MOBILEGL_ASSERT(textureResource != nullptr,
|
||||
"GetOrCreatePipeline: failed to sync color attachment textureId=%d",
|
||||
textureExternalIndex);
|
||||
colorAttachmentFormat = textureResource->format;
|
||||
}
|
||||
|
||||
VkFormatProperties formatProperties{};
|
||||
vkGetPhysicalDeviceFormatProperties(m_physicalDevice.handle, colorAttachmentFormat, &formatProperties);
|
||||
MOBILEGL_ASSERT(
|
||||
(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT) != 0,
|
||||
"GetOrCreatePipeline: blend is enabled on color attachment %u for format=%d textureId=%d, but the format lacks VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT (program=%u)",
|
||||
i,
|
||||
static_cast<Int>(colorAttachmentFormat),
|
||||
textureExternalIndex,
|
||||
program.GetExternalIndex());
|
||||
}
|
||||
payload.colorBlendAttachments[i] = MakeColorBlendAttachmentState(
|
||||
MG_State::pGLContext->IsCapabilityEnabledIndexed(CapabilityInput::Blend, i),
|
||||
effectiveBlendEnabled,
|
||||
MG_Util::ConvertBlendFactorToVkEnum(srcRGB),
|
||||
MG_Util::ConvertBlendFactorToVkEnum(dstRGB),
|
||||
MG_Util::ConvertBlendFactorToVkEnum(srcAlpha),
|
||||
MG_Util::ConvertBlendFactorToVkEnum(dstAlpha),
|
||||
colorWriteMask);
|
||||
attachmentColorWriteMask);
|
||||
}
|
||||
return m_pipelineFactory->GetOrCreatePipeline(payload);
|
||||
}
|
||||
|
||||
@@ -140,6 +140,48 @@ namespace MobileGL::MG_State {
|
||||
return m_vertexArrayState.GetBoundVertexArray();
|
||||
}
|
||||
|
||||
void GLContext::SetCurrentVertexAttributeFloat(Uint index, const Array<Float, 4>& value) {
|
||||
MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(),
|
||||
"SetCurrentVertexAttributeFloat: index %u is out of range", index);
|
||||
|
||||
auto& current = m_currentVertexAttributes[index];
|
||||
current.floatValue = value;
|
||||
for (SizeT component = 0; component < value.size(); ++component) {
|
||||
current.intValue[component] = static_cast<Int32>(value[component]);
|
||||
current.uintValue[component] = static_cast<Uint32>(value[component]);
|
||||
}
|
||||
}
|
||||
|
||||
void GLContext::SetCurrentVertexAttributeInt(Uint index, const Array<Int32, 4>& value) {
|
||||
MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(),
|
||||
"SetCurrentVertexAttributeInt: index %u is out of range", index);
|
||||
|
||||
auto& current = m_currentVertexAttributes[index];
|
||||
current.intValue = value;
|
||||
for (SizeT component = 0; component < value.size(); ++component) {
|
||||
current.floatValue[component] = static_cast<Float>(value[component]);
|
||||
current.uintValue[component] = static_cast<Uint32>(value[component]);
|
||||
}
|
||||
}
|
||||
|
||||
void GLContext::SetCurrentVertexAttributeUint(Uint index, const Array<Uint32, 4>& value) {
|
||||
MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(),
|
||||
"SetCurrentVertexAttributeUint: index %u is out of range", index);
|
||||
|
||||
auto& current = m_currentVertexAttributes[index];
|
||||
current.uintValue = value;
|
||||
for (SizeT component = 0; component < value.size(); ++component) {
|
||||
current.floatValue[component] = static_cast<Float>(value[component]);
|
||||
current.intValue[component] = static_cast<Int32>(value[component]);
|
||||
}
|
||||
}
|
||||
|
||||
const CurrentVertexAttributeValue& GLContext::GetCurrentVertexAttribute(Uint index) const {
|
||||
MOBILEGL_ASSERT(index < m_currentVertexAttributes.size(),
|
||||
"GetCurrentVertexAttribute: index %u is out of range", index);
|
||||
return m_currentVertexAttributes[index];
|
||||
}
|
||||
|
||||
// Texture
|
||||
void GLContext::GenTextureNames(Uint number, Vector<Uint>& textures) {
|
||||
m_textureState.GenerateNames(number, textures);
|
||||
|
||||
@@ -25,6 +25,12 @@ namespace MobileGL {
|
||||
void Init();
|
||||
|
||||
namespace GLState {
|
||||
struct CurrentVertexAttributeValue {
|
||||
Array<Float, 4> floatValue{0.f, 0.f, 0.f, 1.f};
|
||||
Array<Int32, 4> intValue{0, 0, 0, 1};
|
||||
Array<Uint32, 4> uintValue{0u, 0u, 0u, 1u};
|
||||
};
|
||||
|
||||
class GLContext {
|
||||
public:
|
||||
GLContext() = default;
|
||||
@@ -61,6 +67,10 @@ namespace MobileGL {
|
||||
Bool ValidateVertexArrayName(Uint index) const;
|
||||
Bool ValidateVertexArrayObject(Uint index) const;
|
||||
const SharedPtr<VertexArrayObject>& GetBoundVertexArray();
|
||||
void SetCurrentVertexAttributeFloat(Uint index, const Array<Float, 4>& value);
|
||||
void SetCurrentVertexAttributeInt(Uint index, const Array<Int32, 4>& value);
|
||||
void SetCurrentVertexAttributeUint(Uint index, const Array<Uint32, 4>& value);
|
||||
const CurrentVertexAttributeValue& GetCurrentVertexAttribute(Uint index) const;
|
||||
|
||||
// Texture
|
||||
void GenTextureNames(Uint number, Vector<Uint>& textures);
|
||||
@@ -152,6 +162,7 @@ namespace MobileGL {
|
||||
ErrorState m_errorState;
|
||||
BufferState m_bufferState;
|
||||
VertexArrayState m_vertexArrayState;
|
||||
Array<CurrentVertexAttributeValue, VertexArrayObject::MAX_VERTEX_ATTRIBS> m_currentVertexAttributes{};
|
||||
TextureState m_textureState;
|
||||
ProgramState m_programState;
|
||||
RenderState m_renderState;
|
||||
|
||||
@@ -18,6 +18,46 @@ layout(location = 0) out vec4 FragColor;
|
||||
void main() {}
|
||||
)";
|
||||
|
||||
namespace {
|
||||
static int GetVertexInputLocationSpan(GLenum glType) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT_MAT2:
|
||||
case GL_FLOAT_MAT2x3:
|
||||
case GL_FLOAT_MAT2x4:
|
||||
return 2;
|
||||
case GL_FLOAT_MAT3:
|
||||
case GL_FLOAT_MAT3x2:
|
||||
case GL_FLOAT_MAT3x4:
|
||||
return 3;
|
||||
case GL_FLOAT_MAT4:
|
||||
case GL_FLOAT_MAT4x2:
|
||||
case GL_FLOAT_MAT4x3:
|
||||
return 4;
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static GLenum GetVertexInputLocationType(GLenum glType) {
|
||||
switch (glType) {
|
||||
case GL_FLOAT_MAT2:
|
||||
case GL_FLOAT_MAT3x2:
|
||||
case GL_FLOAT_MAT4x2:
|
||||
return GL_FLOAT_VEC2;
|
||||
case GL_FLOAT_MAT3:
|
||||
case GL_FLOAT_MAT2x3:
|
||||
case GL_FLOAT_MAT4x3:
|
||||
return GL_FLOAT_VEC3;
|
||||
case GL_FLOAT_MAT4:
|
||||
case GL_FLOAT_MAT2x4:
|
||||
case GL_FLOAT_MAT3x4:
|
||||
return GL_FLOAT_VEC4;
|
||||
default:
|
||||
return glType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
bool ProgramObject::ShaderIsAttached(const SharedPtr<ShaderObject>& shader) {
|
||||
MGLOG_D("ProgramObject %u: ShaderIsAttached check for shader %p", m_externalIndex, shader.get());
|
||||
@@ -264,7 +304,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
Int maxLoc = -1;
|
||||
for (int i = 0; i < inCount; ++i) {
|
||||
Int loc = (Int)m_program->getPipeInput(i).layoutLocation();
|
||||
if (loc >= 0 && loc != glslang::TQualifier::layoutLocationEnd) maxLoc = std::max(maxLoc, loc);
|
||||
if (loc >= 0 && loc != glslang::TQualifier::layoutLocationEnd) {
|
||||
const Int locationSpan = GetVertexInputLocationSpan(m_program->getPipeInput(i).glDefineType);
|
||||
maxLoc = std::max(maxLoc, loc + locationSpan - 1);
|
||||
}
|
||||
MGLOG_D("ProgramObject %u: Reflection - pipe input[%d] name='%s' layoutLocation=%d glType=%u",
|
||||
m_externalIndex, i, m_program->getPipeInput(i).name.c_str(), loc,
|
||||
m_program->getPipeInput(i).glDefineType);
|
||||
@@ -294,10 +337,25 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_attribInNameMaxLength = std::max(m_attribInNameMaxLength, (Int)inVar.name.length());
|
||||
|
||||
if (location >= 0 && location < (int)m_attribs.size()) {
|
||||
m_attribs[location] = inVar.name;
|
||||
m_attribTypes[location] = inVar.glDefineType;
|
||||
MGLOG_D("ProgramObject %u: Reflection - got attrib '%s' at explicit location %d", m_externalIndex,
|
||||
inVar.name.c_str(), location);
|
||||
const Int locationSpan = GetVertexInputLocationSpan(inVar.glDefineType);
|
||||
const GLenum locationType = GetVertexInputLocationType(inVar.glDefineType);
|
||||
for (Int locationOffset = 0; locationOffset < locationSpan; ++locationOffset) {
|
||||
const Int expandedLocation = location + locationOffset;
|
||||
if (expandedLocation < 0 || expandedLocation >= static_cast<Int>(m_attribs.size())) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_attribs[expandedLocation] = inVar.name;
|
||||
m_attribTypes[expandedLocation] = locationType;
|
||||
MGLOG_D(
|
||||
"ProgramObject %u: Reflection - got attrib '%s' at expanded location %d (baseLocation=%d glType=%u expandedType=%u)",
|
||||
m_externalIndex,
|
||||
inVar.name.c_str(),
|
||||
expandedLocation,
|
||||
location,
|
||||
inVar.glDefineType,
|
||||
static_cast<Uint32>(locationType));
|
||||
}
|
||||
}
|
||||
// else if (location >= (int)m_attribs.size()) {
|
||||
// MGLOG_W("ProgramObject %u: ProgramObject::DoReflection - attrib location %d >= attribs.size()
|
||||
|
||||
@@ -63,6 +63,48 @@ namespace MobileGL::MG_State::GLState {
|
||||
const auto it = std::find(m_attribs.begin(), m_attribs.end(), name);
|
||||
return (it == m_attribs.end()) ? -1 : (Int)std::distance(m_attribs.begin(), it);
|
||||
}
|
||||
Uint32 GetActiveAttributeLocationMask() const {
|
||||
Uint32 mask = 0;
|
||||
const SizeT count = std::min<SizeT>(m_attribs.size(), 32);
|
||||
for (SizeT index = 0; index < count; ++index) {
|
||||
if (!m_attribs[index].empty()) {
|
||||
mask |= (1u << index);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
Uint32 GetActiveFragmentOutputLocationMask() const {
|
||||
if (!m_program) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
Uint32 mask = 0;
|
||||
const Int outputCount = m_program->getNumPipeOutputs();
|
||||
for (Int index = 0; index < outputCount; ++index) {
|
||||
const Int location = static_cast<Int>(m_program->getPipeOutput(index).layoutLocation());
|
||||
if (location >= 0 && location < 32) {
|
||||
mask |= (1u << location);
|
||||
}
|
||||
}
|
||||
return mask;
|
||||
}
|
||||
Int GetActiveFragmentOutputCount() const {
|
||||
return m_program ? m_program->getNumPipeOutputs() : 0;
|
||||
}
|
||||
Int GetFragmentOutputLocation(Uint index) const {
|
||||
MOBILEGL_ASSERT(m_program != nullptr, "ProgramObject::GetFragmentOutputLocation: program is null");
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(m_program->getNumPipeOutputs()),
|
||||
"ProgramObject::GetFragmentOutputLocation: index=%u out of range",
|
||||
index);
|
||||
return static_cast<Int>(m_program->getPipeOutput(static_cast<Int>(index)).layoutLocation());
|
||||
}
|
||||
GLenum GetFragmentOutputType(Uint index) const {
|
||||
MOBILEGL_ASSERT(m_program != nullptr, "ProgramObject::GetFragmentOutputType: program is null");
|
||||
MOBILEGL_ASSERT(index < static_cast<Uint>(m_program->getNumPipeOutputs()),
|
||||
"ProgramObject::GetFragmentOutputType: index=%u out of range",
|
||||
index);
|
||||
return m_program->getPipeOutput(static_cast<Int>(index)).glDefineType;
|
||||
}
|
||||
GLenum GetAttribType(Uint index) const { return m_attribTypes[index]; }
|
||||
const String& GetAttribName(Uint index) const { return m_attribs[index]; }
|
||||
void* MapUBO() { return m_globalUboScratch.data(); }
|
||||
|
||||
@@ -153,6 +153,9 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::R16I:
|
||||
case TextureInternalFormat::R16UI:
|
||||
case TextureInternalFormat::R16F:
|
||||
case TextureInternalFormat::R32F:
|
||||
case TextureInternalFormat::R32I:
|
||||
case TextureInternalFormat::R32UI:
|
||||
return 1;
|
||||
case TextureInternalFormat::RG:
|
||||
case TextureInternalFormat::RG8:
|
||||
@@ -173,6 +176,9 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::RGB5:
|
||||
case TextureInternalFormat::RGB8:
|
||||
case TextureInternalFormat::RGB8Snorm:
|
||||
case TextureInternalFormat::RGB10:
|
||||
case TextureInternalFormat::RGB12:
|
||||
case TextureInternalFormat::RGB16:
|
||||
case TextureInternalFormat::SRGB8:
|
||||
case TextureInternalFormat::RGB8I:
|
||||
case TextureInternalFormat::RGB8UI:
|
||||
@@ -192,6 +198,7 @@ namespace MobileGL {
|
||||
case TextureInternalFormat::RGBA8Snorm:
|
||||
case TextureInternalFormat::RGBA8I:
|
||||
case TextureInternalFormat::RGBA8UI:
|
||||
case TextureInternalFormat::SRGB8Alpha8:
|
||||
case TextureInternalFormat::RGBA12:
|
||||
case TextureInternalFormat::RGBA16:
|
||||
case TextureInternalFormat::RGBA16I:
|
||||
|
||||
@@ -13,10 +13,253 @@
|
||||
namespace {
|
||||
using MobileGL::SizeT;
|
||||
|
||||
struct FlattenedVaryingMember {
|
||||
const char* typeName;
|
||||
const char* memberName;
|
||||
};
|
||||
|
||||
constexpr FlattenedVaryingMember kDailyWeatherVariationMembers[] = {
|
||||
{"vec2", "clouds_cumulus_coverage"},
|
||||
{"vec2", "clouds_altocumulus_coverage"},
|
||||
{"vec2", "clouds_cirrus_coverage"},
|
||||
{"float", "clouds_cumulus_congestus_amount"},
|
||||
{"float", "clouds_stratus_amount"},
|
||||
{"float", "fogginess"},
|
||||
{"float", "aurora_amount"},
|
||||
{"float", "nlc_amount"},
|
||||
{"mat2x3", "aurora_colors"},
|
||||
};
|
||||
|
||||
bool IsIdentifierChar(char ch) {
|
||||
return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || ch == '_';
|
||||
}
|
||||
|
||||
bool HasIdentifierBoundaries(const MobileGL::String& source, SizeT pos, SizeT length) {
|
||||
const bool hasLeftBoundary = pos == 0 || !IsIdentifierChar(source[pos - 1]);
|
||||
const SizeT end = pos + length;
|
||||
const bool hasRightBoundary = end >= source.size() || !IsIdentifierChar(source[end]);
|
||||
return hasLeftBoundary && hasRightBoundary;
|
||||
}
|
||||
|
||||
SizeT FindToken(const MobileGL::String& source, const MobileGL::String& token, SizeT start = 0) {
|
||||
SizeT pos = start;
|
||||
while ((pos = source.find(token, pos)) != MobileGL::String::npos) {
|
||||
if (HasIdentifierBoundaries(source, pos, token.size())) {
|
||||
return pos;
|
||||
}
|
||||
pos += token.size();
|
||||
}
|
||||
return MobileGL::String::npos;
|
||||
}
|
||||
|
||||
void ReplaceTokenOccurrencesInRange(MobileGL::String& source, SizeT rangeStart, SizeT rangeEnd,
|
||||
const MobileGL::String& from, const MobileGL::String& to) {
|
||||
SizeT pos = rangeStart;
|
||||
while ((pos = source.find(from, pos)) != MobileGL::String::npos && pos < rangeEnd) {
|
||||
if (!HasIdentifierBoundaries(source, pos, from.size())) {
|
||||
pos += from.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
source.replace(pos, from.size(), to);
|
||||
const auto delta = static_cast<std::ptrdiff_t>(to.size()) - static_cast<std::ptrdiff_t>(from.size());
|
||||
rangeEnd = static_cast<SizeT>(static_cast<std::ptrdiff_t>(rangeEnd) + delta);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceAll(MobileGL::String& source, const MobileGL::String& from, const MobileGL::String& to) {
|
||||
SizeT pos = 0;
|
||||
while ((pos = source.find(from, pos)) != MobileGL::String::npos) {
|
||||
source.replace(pos, from.size(), to);
|
||||
pos += to.size();
|
||||
}
|
||||
}
|
||||
|
||||
MobileGL::String TrimWhitespace(const MobileGL::String& input) {
|
||||
SizeT begin = 0;
|
||||
while (begin < input.size() && std::isspace(static_cast<unsigned char>(input[begin]))) {
|
||||
begin++;
|
||||
}
|
||||
|
||||
SizeT end = input.size();
|
||||
while (end > begin && std::isspace(static_cast<unsigned char>(input[end - 1]))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return input.substr(begin, end - begin);
|
||||
}
|
||||
|
||||
bool FindFunctionBody(const MobileGL::String& source, const MobileGL::String& signature, SizeT* bodyStart,
|
||||
SizeT* bodyEnd) {
|
||||
const SizeT signaturePos = source.find(signature);
|
||||
if (signaturePos == MobileGL::String::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SizeT bracePos = source.find('{', signaturePos + signature.size());
|
||||
if (bracePos == MobileGL::String::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int depth = 1;
|
||||
for (SizeT pos = bracePos + 1; pos < source.size(); pos++) {
|
||||
if (source[pos] == '{') {
|
||||
depth++;
|
||||
} else if (source[pos] == '}') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
*bodyStart = bracePos + 1;
|
||||
*bodyEnd = pos;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void RenameDailyWeatherVariationHelperLocal(MobileGL::String& source) {
|
||||
constexpr const char* kHelperSignature = "DailyWeatherVariation get_daily_weather_variation()";
|
||||
constexpr const char* kInterfaceName = "daily_weather_variation";
|
||||
constexpr const char* kLocalName = "mg_daily_weather_variation_local";
|
||||
|
||||
SizeT bodyStart = 0;
|
||||
SizeT bodyEnd = 0;
|
||||
if (!FindFunctionBody(source, kHelperSignature, &bodyStart, &bodyEnd)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ReplaceTokenOccurrencesInRange(source, bodyStart, bodyEnd, kInterfaceName, kLocalName);
|
||||
}
|
||||
|
||||
bool RewriteDailyWeatherVariationInterface(MobileGL::ShaderStage stage, MobileGL::String& source) {
|
||||
using MobileGL::ShaderStage;
|
||||
|
||||
if (stage != ShaderStage::Vertex && stage != ShaderStage::Fragment) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr const char* kTypeName = "DailyWeatherVariation";
|
||||
constexpr const char* kInterfaceName = "daily_weather_variation";
|
||||
constexpr const char* kTempName = "mg_daily_weather_variation_tmp";
|
||||
const MobileGL::String declarationNeedle = MobileGL::String(kTypeName) + " " + kInterfaceName + ";";
|
||||
|
||||
RenameDailyWeatherVariationHelperLocal(source);
|
||||
|
||||
const SizeT declarationPos = source.find(declarationNeedle);
|
||||
if (declarationPos == MobileGL::String::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
SizeT lineStart = source.rfind('\n', declarationPos);
|
||||
lineStart = (lineStart == MobileGL::String::npos) ? 0 : lineStart + 1;
|
||||
SizeT lineEnd = source.find('\n', declarationPos);
|
||||
if (lineEnd == MobileGL::String::npos) {
|
||||
lineEnd = source.size();
|
||||
}
|
||||
|
||||
const MobileGL::String declarationLine = source.substr(lineStart, lineEnd - lineStart);
|
||||
MOBILEGL_ASSERT(declarationLine.find("layout(") == MobileGL::String::npos,
|
||||
"PreprocessShaderSource: unexpected explicit layout on DailyWeatherVariation interface in stage=%d",
|
||||
static_cast<int>(stage));
|
||||
|
||||
const bool hasInputQualifier = declarationLine.find(" in ") != MobileGL::String::npos ||
|
||||
declarationLine.rfind("in ", 0) == 0;
|
||||
const bool hasOutputQualifier = declarationLine.find(" out ") != MobileGL::String::npos ||
|
||||
declarationLine.rfind("out ", 0) == 0;
|
||||
MOBILEGL_ASSERT(hasInputQualifier != hasOutputQualifier,
|
||||
"PreprocessShaderSource: expected a single in/out qualifier on DailyWeatherVariation interface in stage=%d line='%s'",
|
||||
static_cast<int>(stage), declarationLine.c_str());
|
||||
|
||||
const MobileGL::String qualifierPrefix = source.substr(lineStart, declarationPos - lineStart);
|
||||
MobileGL::String replacementDeclaration;
|
||||
for (const auto& member : kDailyWeatherVariationMembers) {
|
||||
replacementDeclaration += qualifierPrefix;
|
||||
replacementDeclaration += member.typeName;
|
||||
replacementDeclaration += " ";
|
||||
replacementDeclaration += kInterfaceName;
|
||||
replacementDeclaration += "_";
|
||||
replacementDeclaration += member.memberName;
|
||||
replacementDeclaration += ";\n";
|
||||
}
|
||||
source.replace(lineStart, lineEnd - lineStart + (lineEnd < source.size() ? 1 : 0), replacementDeclaration);
|
||||
|
||||
SizeT assignPos = FindToken(source, kInterfaceName);
|
||||
while (assignPos != MobileGL::String::npos) {
|
||||
SizeT probe = assignPos + strlen(kInterfaceName);
|
||||
while (probe < source.size() && std::isspace(static_cast<unsigned char>(source[probe]))) {
|
||||
probe++;
|
||||
}
|
||||
|
||||
if (probe >= source.size() || source[probe] != '=') {
|
||||
assignPos = FindToken(source, kInterfaceName, assignPos + strlen(kInterfaceName));
|
||||
continue;
|
||||
}
|
||||
|
||||
SizeT statementStart = source.rfind('\n', assignPos);
|
||||
statementStart = (statementStart == MobileGL::String::npos) ? 0 : statementStart + 1;
|
||||
for (SizeT i = statementStart; i < assignPos; i++) {
|
||||
MOBILEGL_ASSERT(std::isspace(static_cast<unsigned char>(source[i])),
|
||||
"PreprocessShaderSource: unexpected inline DailyWeatherVariation assignment in stage=%d",
|
||||
static_cast<int>(stage));
|
||||
}
|
||||
|
||||
const MobileGL::String indentation = source.substr(statementStart, assignPos - statementStart);
|
||||
const SizeT statementEnd = source.find(';', probe);
|
||||
MOBILEGL_ASSERT(statementEnd != MobileGL::String::npos,
|
||||
"PreprocessShaderSource: missing ';' after DailyWeatherVariation assignment in stage=%d",
|
||||
static_cast<int>(stage));
|
||||
|
||||
const MobileGL::String rhsExpression = TrimWhitespace(source.substr(probe + 1, statementEnd - probe - 1));
|
||||
MobileGL::String replacementStatement;
|
||||
replacementStatement += indentation;
|
||||
replacementStatement += "{\n";
|
||||
replacementStatement += indentation;
|
||||
replacementStatement += " DailyWeatherVariation ";
|
||||
replacementStatement += kTempName;
|
||||
replacementStatement += " = ";
|
||||
replacementStatement += rhsExpression;
|
||||
replacementStatement += ";\n";
|
||||
for (const auto& member : kDailyWeatherVariationMembers) {
|
||||
replacementStatement += indentation;
|
||||
replacementStatement += " ";
|
||||
replacementStatement += kInterfaceName;
|
||||
replacementStatement += "_";
|
||||
replacementStatement += member.memberName;
|
||||
replacementStatement += " = ";
|
||||
replacementStatement += kTempName;
|
||||
replacementStatement += ".";
|
||||
replacementStatement += member.memberName;
|
||||
replacementStatement += ";\n";
|
||||
}
|
||||
replacementStatement += indentation;
|
||||
replacementStatement += "}";
|
||||
if (statementEnd + 1 < source.size() && source[statementEnd + 1] == '\n') {
|
||||
replacementStatement += "\n";
|
||||
source.replace(statementStart, statementEnd - statementStart + 2, replacementStatement);
|
||||
} else {
|
||||
source.replace(statementStart, statementEnd - statementStart + 1, replacementStatement);
|
||||
}
|
||||
|
||||
assignPos = FindToken(source, kInterfaceName, statementStart + replacementStatement.size());
|
||||
}
|
||||
|
||||
for (const auto& member : kDailyWeatherVariationMembers) {
|
||||
const MobileGL::String from = MobileGL::String(kInterfaceName) + "." + member.memberName;
|
||||
const MobileGL::String to = MobileGL::String(kInterfaceName) + "_" + member.memberName;
|
||||
ReplaceAll(source, from, to);
|
||||
}
|
||||
|
||||
MOBILEGL_ASSERT(source.find(MobileGL::String(kTypeName) + " " + kInterfaceName + ";") == MobileGL::String::npos,
|
||||
"PreprocessShaderSource: unrewritten DailyWeatherVariation interface declaration remained in stage=%d",
|
||||
static_cast<int>(stage));
|
||||
MOBILEGL_ASSERT(source.find(MobileGL::String(kInterfaceName) + ".") == MobileGL::String::npos,
|
||||
"PreprocessShaderSource: unrewritten DailyWeatherVariation member access remained in stage=%d",
|
||||
static_cast<int>(stage));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasSingleLineFunctionDefinition(const MobileGL::String& source, const MobileGL::String& functionName) {
|
||||
SizeT lineStart = 0;
|
||||
while (lineStart < source.size()) {
|
||||
@@ -164,6 +407,8 @@ namespace MobileGL {
|
||||
RenameBuiltinShadowingFunction(source, "round", "mg_round");
|
||||
RenameBuiltinShadowingFunction(source, "tanh", "mg_tanh");
|
||||
RenameBuiltinShadowingFunction(source, "fma", "mg_fma");
|
||||
|
||||
RewriteDailyWeatherVariationInterface(stage, source);
|
||||
}
|
||||
|
||||
} // namespace ShaderTranspiler
|
||||
|
||||
Reference in New Issue
Block a user