mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (MG_Backend, MG_Impl, ShaderTranspiler, MG_Test): support iterationRP custom images and storage format reinterpretation
This commit is contained in:
@@ -865,6 +865,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
GLint maxUniformBlockSize = 16384;
|
||||
GLint maxImageUnits = 8;
|
||||
GLint maxCombinedImageUniforms = 8;
|
||||
GLint maxVertexImageUniforms = 0;
|
||||
GLint maxGeometryImageUniforms = 0;
|
||||
GLint maxFragmentImageUniforms = 8;
|
||||
GLint maxComputeImageUniforms = 8;
|
||||
GLint maxDrawBuffers = 8;
|
||||
GLint maxColorAttachments = 8;
|
||||
@@ -904,7 +907,16 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
glesFuncs.glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_IMAGE_UNITS, &maxImageUnits);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_COMBINED_IMAGE_UNIFORMS, &maxCombinedImageUniforms);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &maxVertexImageUniforms);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImageUniforms);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms);
|
||||
// Geometry shaders and their image-uniform query are core only in ES 3.2. DirectGLES
|
||||
// emits ESSL 3.10 on an ES 3.1 context, so reporting zero there is both legal and an
|
||||
// accurate description of what the backend compiler can consume.
|
||||
if (caps.GLESVersion.Major > 3 ||
|
||||
(caps.GLESVersion.Major == 3 && caps.GLESVersion.Minor >= 2)) {
|
||||
glesFuncs.glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS, &maxGeometryImageUniforms);
|
||||
}
|
||||
glesFuncs.glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, &maxColorAttachments);
|
||||
glesFuncs.glGetIntegerv(GL_MAX_CLIP_DISTANCES, &maxClipDistances);
|
||||
@@ -955,6 +967,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.MaxUniformBlockSize = maxUniformBlockSize;
|
||||
caps.MaxImageUnits = maxImageUnits;
|
||||
caps.MaxCombinedImageUniforms = maxCombinedImageUniforms;
|
||||
caps.MaxVertexImageUniforms = maxVertexImageUniforms;
|
||||
caps.MaxGeometryImageUniforms = maxGeometryImageUniforms;
|
||||
caps.MaxFragmentImageUniforms = maxFragmentImageUniforms;
|
||||
caps.MaxComputeImageUniforms = maxComputeImageUniforms;
|
||||
caps.MaxDrawBuffers = maxDrawBuffers;
|
||||
caps.MaxColorAttachments = maxColorAttachments;
|
||||
@@ -1000,6 +1015,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
MGLOG_I(" GL_MAX_UNIFORM_BLOCK_SIZE: %d", caps.MaxUniformBlockSize);
|
||||
MGLOG_I(" GL_MAX_IMAGE_UNITS: %d", caps.MaxImageUnits);
|
||||
MGLOG_I(" GL_MAX_COMBINED_IMAGE_UNIFORMS: %d", caps.MaxCombinedImageUniforms);
|
||||
MGLOG_I(" GL_MAX_VERTEX_IMAGE_UNIFORMS: %d", caps.MaxVertexImageUniforms);
|
||||
MGLOG_I(" GL_MAX_GEOMETRY_IMAGE_UNIFORMS: %d", caps.MaxGeometryImageUniforms);
|
||||
MGLOG_I(" GL_MAX_FRAGMENT_IMAGE_UNIFORMS: %d", caps.MaxFragmentImageUniforms);
|
||||
MGLOG_I(" GL_MAX_COMPUTE_IMAGE_UNIFORMS: %d", caps.MaxComputeImageUniforms);
|
||||
MGLOG_I(" GL_MAX_DRAW_BUFFERS: %d", caps.MaxDrawBuffers);
|
||||
MGLOG_I(" GL_MAX_COLOR_ATTACHMENTS: %d", caps.MaxColorAttachments);
|
||||
|
||||
@@ -1102,6 +1102,9 @@ namespace MobileGL {
|
||||
Int MaxUniformBlockSize = 16384;
|
||||
Int MaxImageUnits = 8;
|
||||
Int MaxCombinedImageUniforms = 8;
|
||||
Int MaxVertexImageUniforms = 0;
|
||||
Int MaxGeometryImageUniforms = 0;
|
||||
Int MaxFragmentImageUniforms = 8;
|
||||
Int MaxComputeImageUniforms = 8;
|
||||
Int MaxDrawBuffers = 8;
|
||||
Int MaxColorAttachments = 8;
|
||||
|
||||
@@ -174,6 +174,10 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
VkPhysicalDeviceFeatures supportedFeatures{};
|
||||
vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
|
||||
caps.SupportsWideLines = supportedFeatures.wideLines == VK_TRUE;
|
||||
caps.SupportsVertexPipelineStoresAndAtomics =
|
||||
supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE;
|
||||
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
|
||||
caps.SupportsGeometryShader = supportedFeatures.geometryShader == VK_TRUE;
|
||||
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(p.limits.maxStorageBufferRange);
|
||||
const Bool supportsShaderSubgroup = vk.vkGetPhysicalDeviceProperties2 &&
|
||||
HasUsableShaderSubgroupSupport(subgroupProps);
|
||||
@@ -256,6 +260,11 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.ViewportBoundsRangeMax = properties.limits.viewportBoundsRange[1];
|
||||
caps.ViewportSubpixelBits = static_cast<Int>(properties.limits.viewportSubPixelBits);
|
||||
caps.SupportsWideLines = false;
|
||||
// This helper only receives properties, not VkPhysicalDeviceFeatures. Leave optional
|
||||
// stage writes disabled rather than inferring them from descriptor limits alone.
|
||||
caps.SupportsVertexPipelineStoresAndAtomics = false;
|
||||
caps.SupportsFragmentStoresAndAtomics = false;
|
||||
caps.SupportsGeometryShader = false;
|
||||
caps.MaxShaderStorageBlockSize = static_cast<SizeT>(properties.limits.maxStorageBufferRange);
|
||||
caps.SupportsShaderSubgroup = false;
|
||||
caps.SubgroupSize = 0;
|
||||
|
||||
@@ -67,6 +67,12 @@ namespace MobileGL {
|
||||
Float ViewportBoundsRangeMax = 0.0f;
|
||||
Int ViewportSubpixelBits = 0;
|
||||
Bool SupportsWideLines = false;
|
||||
// Storage-image descriptors are limited per stage by
|
||||
// maxPerStageDescriptorStorageImages, but writes/atomics outside compute additionally
|
||||
// require these core Vulkan features to be enabled on the logical device.
|
||||
Bool SupportsVertexPipelineStoresAndAtomics = false;
|
||||
Bool SupportsFragmentStoresAndAtomics = false;
|
||||
Bool SupportsGeometryShader = false;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Bool SupportsShaderSubgroup = false;
|
||||
Uint32 SubgroupSize = 0;
|
||||
|
||||
@@ -6,6 +6,10 @@
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#define SPV_ENABLE_UTILITY_CODE
|
||||
#include "glslang/SPIRV/spirv.hpp11"
|
||||
#undef SPV_ENABLE_UTILITY_CODE
|
||||
|
||||
#include "ShaderCompiler.h"
|
||||
|
||||
#include "SpirvPasses/EliminateFloatEqualsZeroPass.h"
|
||||
@@ -19,6 +23,7 @@
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include "ShaderSourceProcessor.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
|
||||
#include <MG_Util/Converters/GLToGlslang/ProgramEnumConverter.h>
|
||||
#include <cstdlib>
|
||||
@@ -26,8 +31,8 @@
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
TBuiltInResource& GetTBuiltInResourceInstance() {
|
||||
static TBuiltInResource Resources{};
|
||||
TBuiltInResource BuildTBuiltInResource() {
|
||||
TBuiltInResource Resources{};
|
||||
Resources.maxLights = 32;
|
||||
Resources.maxClipPlanes = 6;
|
||||
Resources.maxTextureUnits = 32;
|
||||
@@ -122,6 +127,22 @@ namespace MobileGL {
|
||||
Resources.maxTaskWorkGroupSizeZ_NV = 1;
|
||||
Resources.maxMeshViewCountNV = 4;
|
||||
|
||||
// Resource checking must describe the same backend contract exposed through
|
||||
// glGetIntegerv. Keeping this copy local also avoids racing on a process-global
|
||||
// TBuiltInResource when Iris compiles shaders concurrently.
|
||||
const MG_Backend::DynamicBackendParameters fallbackParameters{};
|
||||
const auto& activeBackend = MG_Backend::pActiveBackendObject;
|
||||
const auto& dynamicParameters =
|
||||
activeBackend ? activeBackend->GetDynamicParameters() : fallbackParameters;
|
||||
Resources.maxImageUnits = dynamicParameters.MaxImageUnits;
|
||||
Resources.maxCombinedImageUnitsAndFragmentOutputs =
|
||||
dynamicParameters.MaxImageUnits + dynamicParameters.MaxDrawBuffers;
|
||||
Resources.maxVertexImageUniforms = dynamicParameters.MaxVertexImageUniforms;
|
||||
Resources.maxGeometryImageUniforms = dynamicParameters.MaxGeometryImageUniforms;
|
||||
Resources.maxFragmentImageUniforms = dynamicParameters.MaxFragmentImageUniforms;
|
||||
Resources.maxComputeImageUniforms = dynamicParameters.MaxComputeImageUniforms;
|
||||
Resources.maxCombinedImageUniforms = dynamicParameters.MaxCombinedImageUniforms;
|
||||
|
||||
Resources.limits.nonInductiveForLoops = true;
|
||||
Resources.limits.whileLoops = true;
|
||||
Resources.limits.doWhileLoops = true;
|
||||
@@ -167,7 +188,8 @@ namespace MobileGL {
|
||||
tshader->setAutoMapLocations(true);
|
||||
tshader->setAutoMapBindings(true);
|
||||
tshader->setGlobalUniformBlockName(GLOBAL_UBO_NAME);
|
||||
if (!tshader->parse(&GetTBuiltInResourceInstance(), 460, ECoreProfile,
|
||||
auto resources = BuildTBuiltInResource();
|
||||
if (!tshader->parse(&resources, 460, ECoreProfile,
|
||||
/*forceDefaultVersionAndProfile: */ false,
|
||||
/*forwardCompatible: */ true, EShMsgDefault)) {
|
||||
ResultInfo r;
|
||||
@@ -392,6 +414,136 @@ namespace MobileGL {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderCompiler::UseUnformattedFloatStorageImagesForVulkan(
|
||||
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary) {
|
||||
constexpr SizeT kSpirvHeaderWordCount = 5;
|
||||
outputBinary.clear();
|
||||
if (inputBinary.size() < kSpirvHeaderWordCount || inputBinary[0] != spv::MagicNumber) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<Uint32> floatTypeIds;
|
||||
Vector<Uint32> resultTypeById(inputBinary[3], 0);
|
||||
Vector<Uint32> pointerPointeeTypeById(inputBinary[3], 0);
|
||||
Bool hasReadWithoutFormatCapability = false;
|
||||
Bool hasWriteWithoutFormatCapability = false;
|
||||
SizeT capabilityInsertOffset = kSpirvHeaderWordCount;
|
||||
|
||||
for (SizeT offset = kSpirvHeaderWordCount; offset < inputBinary.size();) {
|
||||
const Uint32 instructionWord = inputBinary[offset];
|
||||
const Uint32 wordCount = instructionWord >> 16u;
|
||||
const auto opcode = static_cast<spv::Op>(instructionWord & 0xffffu);
|
||||
if (wordCount == 0 || offset + wordCount > inputBinary.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (opcode == spv::Op::OpCapability && wordCount >= 2) {
|
||||
capabilityInsertOffset = offset + wordCount;
|
||||
const auto capability = static_cast<spv::Capability>(inputBinary[offset + 1]);
|
||||
hasReadWithoutFormatCapability |=
|
||||
capability == spv::Capability::StorageImageReadWithoutFormat;
|
||||
hasWriteWithoutFormatCapability |=
|
||||
capability == spv::Capability::StorageImageWriteWithoutFormat;
|
||||
} else if (opcode == spv::Op::OpTypeFloat && wordCount >= 3) {
|
||||
floatTypeIds.push_back(inputBinary[offset + 1]);
|
||||
} else if (opcode == spv::Op::OpTypePointer && wordCount >= 4) {
|
||||
const Uint32 pointerTypeId = inputBinary[offset + 1];
|
||||
if (pointerTypeId >= pointerPointeeTypeById.size()) {
|
||||
return false;
|
||||
}
|
||||
pointerPointeeTypeById[pointerTypeId] = inputBinary[offset + 3];
|
||||
}
|
||||
|
||||
bool hasResult = false;
|
||||
bool hasResultType = false;
|
||||
spv::HasResultAndType(opcode, &hasResult, &hasResultType);
|
||||
if (hasResult && hasResultType && wordCount >= 3) {
|
||||
const Uint32 resultTypeId = inputBinary[offset + 1];
|
||||
const Uint32 resultId = inputBinary[offset + 2];
|
||||
if (resultId >= resultTypeById.size()) {
|
||||
return false;
|
||||
}
|
||||
resultTypeById[resultId] = resultTypeId;
|
||||
}
|
||||
offset += wordCount;
|
||||
}
|
||||
|
||||
// OpImageTexelPointer is the bridge to image atomic instructions. Vulkan requires
|
||||
// those image types to retain an atomic-compatible declared format, so exclude only
|
||||
// the exact image types used by an atomic path rather than disabling formatless
|
||||
// access for unrelated float images in the same module.
|
||||
Vector<Uint32> atomicImageTypeIds;
|
||||
for (SizeT offset = kSpirvHeaderWordCount; offset < inputBinary.size();) {
|
||||
const Uint32 instructionWord = inputBinary[offset];
|
||||
const Uint32 wordCount = instructionWord >> 16u;
|
||||
const auto opcode = static_cast<spv::Op>(instructionWord & 0xffffu);
|
||||
if (opcode == spv::Op::OpImageTexelPointer && wordCount >= 6) {
|
||||
const Uint32 imageId = inputBinary[offset + 3];
|
||||
if (imageId >= resultTypeById.size()) {
|
||||
return false;
|
||||
}
|
||||
Uint32 imageTypeId = resultTypeById[imageId];
|
||||
if (imageTypeId < pointerPointeeTypeById.size() &&
|
||||
pointerPointeeTypeById[imageTypeId] != 0) {
|
||||
imageTypeId = pointerPointeeTypeById[imageTypeId];
|
||||
}
|
||||
if (imageTypeId != 0 &&
|
||||
std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), imageTypeId) ==
|
||||
atomicImageTypeIds.end()) {
|
||||
atomicImageTypeIds.push_back(imageTypeId);
|
||||
}
|
||||
}
|
||||
offset += wordCount;
|
||||
}
|
||||
|
||||
outputBinary = inputBinary;
|
||||
Bool hasFloatStorageImage = false;
|
||||
for (SizeT offset = kSpirvHeaderWordCount; offset < outputBinary.size();) {
|
||||
const Uint32 instructionWord = outputBinary[offset];
|
||||
const Uint32 wordCount = instructionWord >> 16u;
|
||||
const auto opcode = static_cast<spv::Op>(instructionWord & 0xffffu);
|
||||
|
||||
// OpTypeImage operands are: result id, sampled type, dim, depth, arrayed,
|
||||
// multisampled, sampled, image format, and an optional access qualifier.
|
||||
if (opcode == spv::Op::OpTypeImage && wordCount >= 9) {
|
||||
const Uint32 imageTypeId = outputBinary[offset + 1];
|
||||
const Uint32 sampledTypeId = outputBinary[offset + 2];
|
||||
const Uint32 sampled = outputBinary[offset + 7];
|
||||
const Bool hasFloatSampledType =
|
||||
std::find(floatTypeIds.begin(), floatTypeIds.end(), sampledTypeId) != floatTypeIds.end();
|
||||
const Bool usedByAtomic =
|
||||
std::find(atomicImageTypeIds.begin(), atomicImageTypeIds.end(), imageTypeId) !=
|
||||
atomicImageTypeIds.end();
|
||||
if (sampled == 2 && hasFloatSampledType && !usedByAtomic) {
|
||||
outputBinary[offset + 8] = static_cast<Uint32>(spv::ImageFormat::Unknown);
|
||||
hasFloatStorageImage = true;
|
||||
}
|
||||
}
|
||||
offset += wordCount;
|
||||
}
|
||||
|
||||
if (!hasFloatStorageImage) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Vector<Uint32> addedCapabilities;
|
||||
const Uint32 capabilityInstruction =
|
||||
(2u << 16u) | static_cast<Uint32>(spv::Op::OpCapability);
|
||||
if (!hasReadWithoutFormatCapability) {
|
||||
addedCapabilities.push_back(capabilityInstruction);
|
||||
addedCapabilities.push_back(
|
||||
static_cast<Uint32>(spv::Capability::StorageImageReadWithoutFormat));
|
||||
}
|
||||
if (!hasWriteWithoutFormatCapability) {
|
||||
addedCapabilities.push_back(capabilityInstruction);
|
||||
addedCapabilities.push_back(
|
||||
static_cast<Uint32>(spv::Capability::StorageImageWriteWithoutFormat));
|
||||
}
|
||||
outputBinary.insert(outputBinary.begin() + static_cast<std::ptrdiff_t>(capabilityInsertOffset),
|
||||
addedCapabilities.begin(), addedCapabilities.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) {
|
||||
spvc_compiler_options options;
|
||||
session.CreateOptions(&options);
|
||||
|
||||
@@ -46,6 +46,14 @@ namespace MobileGL {
|
||||
// decoration. DirectVulkan only.
|
||||
static bool DecoratePositionInvariantForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
// Replaces the declared format of float storage images with Unknown and adds the
|
||||
// matching SPIR-V capabilities. DirectVulkan uses this only when both Vulkan
|
||||
// shaderStorageImage*WithoutFormat features are enabled, allowing the
|
||||
// glBindImageTexture format to select the descriptor view at runtime. Integer
|
||||
// storage images deliberately keep their declared format for GL-compatible bit
|
||||
// reinterpretation paths (for example, R32F storage accessed as r32ui).
|
||||
static bool UseUnformattedFloatStorageImagesForVulkan(
|
||||
const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary);
|
||||
static Result<String> DecompileShader(SpvcSession& session);
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
|
||||
@@ -63,7 +63,17 @@ namespace MobileGL {
|
||||
void TMglGlslIoResolver::reserverStorageSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) {
|
||||
const glslang::TType& type = ent.symbol->getType();
|
||||
const glslang::TString& name = ent.symbol->getAccessName();
|
||||
if (currentStage == EShLangVertex && type.getQualifier().isPipeInput()) {
|
||||
// OpenGL assigns generic vertex attribute locations only to active inputs. glslang gathers
|
||||
// both live and dead declarations before mapping, so allowing the default collector to
|
||||
// reserve a dead vertex input would make it consume a location that an active input should
|
||||
// reuse. Other stage interfaces still need the default cross-stage matching behavior.
|
||||
if (!ent.live && currentStage == EShLangVertex && type.getQualifier().isPipeInput()) {
|
||||
return;
|
||||
}
|
||||
// glBindAttribLocation only affects active inputs in the linked program. Applying an API
|
||||
// binding to an inactive declaration would reserve its slot in glslang's collector and
|
||||
// incorrectly push an active, automatically mapped input to a different location.
|
||||
if (ent.live && currentStage == EShLangVertex && type.getQualifier().isPipeInput()) {
|
||||
auto it = m_explicitVertexIns.find(name.c_str());
|
||||
if (it != m_explicitVertexIns.end()) {
|
||||
auto& writableType = ent.symbol->getWritableType();
|
||||
@@ -94,6 +104,13 @@ namespace MobileGL {
|
||||
TDefaultGlslIoResolver::reserverStorageSlot(ent, infoSink);
|
||||
}
|
||||
|
||||
int TMglGlslIoResolver::resolveInOutLocation(EShLanguage stage, glslang::TVarEntryInfo& ent) {
|
||||
if (!ent.live && stage == EShLangVertex && ent.symbol->getType().getQualifier().isPipeInput()) {
|
||||
return ent.newLocation = -1;
|
||||
}
|
||||
return TDefaultGlslIoResolver::resolveInOutLocation(stage, ent);
|
||||
}
|
||||
|
||||
void TMglGlslIoResolver::reserverResourceSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) {
|
||||
const glslang::TType& type = ent.symbol->getType();
|
||||
if (m_explicitOpaqueUniformBindings != nullptr && type.getBasicType() == glslang::EbtSampler &&
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace MobileGL {
|
||||
opaqueUniformBindings) {}
|
||||
void reserverStorageSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) override;
|
||||
void reserverResourceSlot(glslang::TVarEntryInfo& ent, TInfoSink& infoSink) override;
|
||||
int resolveInOutLocation(EShLanguage stage, glslang::TVarEntryInfo& ent) override;
|
||||
int resolveUniformLocation(EShLanguage stage, glslang::TVarEntryInfo& ent) override;
|
||||
|
||||
protected:
|
||||
|
||||
Reference in New Issue
Block a user