mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Feat] (MG_State, MG_Backend, MG_Util): feed a 64-bit vertex attribute on DirectVulkan
glVertexAttribLFormat validated its arguments and then refused unconditionally with "64-bit vertex attributes are not supported", so direct_state_access.vertex_arrays_attribute_format failed every GL_DOUBLE subcase on both backends - the format never landed, the draw fetched whatever the attribute held before, and the captured values came back as reinterpreted garbage. The attribute is now real state. IsLong is its own bit rather than being inferred from Float64, because glVertexAttribFormat(GL_DOUBLE) also reads doubles - it just asks for them converted to float - so the type alone cannot tell the two apart. It participates in the format comparison, so an L-format call over a plain one still bumps the version, and glVertexAttribPointer clears it inside the mutation block so the clear and the bump stay atomic. GL_VERTEX_ATTRIB_ARRAY_LONG stops being hardcoded false, and the pname is now accepted by the attribute queries at all. Support is detected, never assumed. SupportsFloat64VertexAttributes comes from VkPhysicalDeviceFeatures::shaderFloat64 on DirectVulkan and is false on DirectGLES - not a driver question there and never will be, since ES has no GL_DOUBLE vertex format and ESSL has no fp64 type to consume one with. A backend without it declines in the entry point, with the GL error and a log line naming the reason, rather than accepting state no draw could honour. Both cases get a DriverPost row so the loss is named at startup instead of at draw setup. On DirectVulkan the attribute deliberately does not use VK_FORMAT_R64*_SFLOAT: those are optional and lavapipe advertises zero features for all four of them. It is fetched as its 32-bit word pair (R32G32_UINT / R32G32B32A32_UINT) and bitcast back to double in the shader by a new SPIR-V pass, which is bit-exact and needs no format capability at all. The pass re-declares the input as uvec2 / uvec4, demotes the original variable to a Private global and seeds it once at the top of the entry point, so every existing load keeps its id and its double type and no other instruction is rewritten. Both halves branch on nothing but "is this attribute long", so they cannot disagree - and if the pass ever fails, the assertion fires rather than letting a UINT format sit under a double input. The pointer types are all created before any variable that names them and the demoted variable is moved after them, since the types-and-variables section may not forward-reference a type. dvec3/dvec4 are declined rather than fetched wrong: six or eight uint32 components have no single VkFormat, and GL spreads such an input over two attribute locations, which the location-per-index model here does not express. Fixes vertex_arrays_attribute_format on Magma (369/371). On Espryt it stays failing, now as a detected and explained decline rather than a blanket refusal.
This commit is contained in:
@@ -190,6 +190,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecoratePositionInvariantPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RebaseInstanceIndexPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/NormalizeRectCoordinatesPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/StripUboMemberRelaxedPrecisionPass.cpp
|
||||
|
||||
@@ -359,6 +359,13 @@ namespace MobileGL {
|
||||
// array layer with no notion of a 3D depth slice, so it does not yet. Defaults to false
|
||||
// so a backend that never sets it gets the conservative answer.
|
||||
Bool SupportsPerLayerFramebufferAttachment = false;
|
||||
// Whether glVertexAttribLFormat / glVertexArrayAttribLFormat can be honoured, i.e.
|
||||
// whether a 64-bit vertex attribute can actually reach a shader unconverted. Detected,
|
||||
// never assumed: DirectVulkan needs VkPhysicalDeviceFeatures::shaderFloat64 (the
|
||||
// attribute travels as its 32-bit word pair, so no VK_FORMAT_R64* is required, but the
|
||||
// bitcast result is Float64); DirectGLES can never have it, ESSL having no fp64 type at
|
||||
// all. Defaults to false so a backend that never sets it gets the conservative answer.
|
||||
Bool SupportsFloat64VertexAttributes = false;
|
||||
SizeT MaxShaderStorageBlockSize = 128 * 1024 * 1024;
|
||||
Uint32 SubgroupSize = 0;
|
||||
Uint32 SubgroupSupportedStages = 0;
|
||||
|
||||
@@ -1114,6 +1114,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// given - provided the driver resolved the entry point at all.
|
||||
m_dynamicParameters.SupportsPerLayerFramebufferAttachment =
|
||||
DirectGLES::g_GLESFuncs.glFramebufferTextureLayer != nullptr;
|
||||
// Not a driver question and never will be: OpenGL ES has no double-precision vertex format
|
||||
// and ESSL has no fp64 type to consume one with, so a 64-bit vertex attribute has nowhere to
|
||||
// land on this backend regardless of what the driver underneath happens to support.
|
||||
m_dynamicParameters.SupportsFloat64VertexAttributes = false;
|
||||
m_dynamicParameters.MaxDrawBuffers = m_GLESCapabilities.MaxDrawBuffers;
|
||||
m_dynamicParameters.MaxColorAttachments = m_GLESCapabilities.MaxColorAttachments;
|
||||
m_dynamicParameters.MaxClipDistances = m_GLESCapabilities.MaxClipDistances;
|
||||
|
||||
@@ -1305,6 +1305,22 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
m_syncedAttributeVersions[attribIndex].BufferVersion;
|
||||
if (!needsSyncFormat && !needsSyncBuffer) continue;
|
||||
|
||||
// Defence in depth. The frontend already declines glVertexAttribLFormat on this
|
||||
// backend (SupportsFloat64VertexAttributes is false - ES has no GL_DOUBLE vertex
|
||||
// format and ESSL has no fp64 type), so IsLong should never arrive here; if it ever
|
||||
// did, passing GL_DOUBLE to glVertexAttribPointer would only raise GL_INVALID_ENUM on
|
||||
// the real driver. Disabling rather than merely skipping matters: becoming long bumps
|
||||
// FormatVersion, not SwitchVersion, so the enable/disable block above will not run
|
||||
// again and an already-enabled array would stay enabled with no pointer and no
|
||||
// ARRAY_BUFFER binding - which ES 3.1+ makes an INVALID_OPERATION at draw.
|
||||
if (attrib.IsLong) {
|
||||
MGLOG_E("DirectGLES: vertex attribute %u is a 64-bit (GL_DOUBLE) array, which this "
|
||||
"backend cannot feed - disabling the array",
|
||||
attribIndex);
|
||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!BindAttributeBuffer(attrib)) {
|
||||
continue;
|
||||
}
|
||||
@@ -1366,6 +1382,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Same reason as SyncToBackend: there is no ES vertex format for a 64-bit array, and
|
||||
// this path only ever reaches glVertexAttribPointer/IPointer.
|
||||
if (attrib.IsLong) {
|
||||
g_GLESFuncs.glDisableVertexAttribArray(attribIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto* clientData = reinterpret_cast<const Uint8*>(attrib.Offset);
|
||||
const SizeT elementSize = GetAttributeByteSize(attrib.Type, attrib.Size, attrib.IsBgra);
|
||||
if (!clientData || elementSize == 0 || attrib.Size <= 0) {
|
||||
|
||||
@@ -810,6 +810,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
m_dynamicParameters.SupportsWideLines = m_vulkanCaps.SupportsWideLines;
|
||||
m_dynamicParameters.SupportsFloat64VertexAttributes = m_vulkanCaps.SupportsShaderFloat64;
|
||||
m_dynamicParameters.MaxShaderStorageBlockSize =
|
||||
std::min(m_vulkanCaps.MaxShaderStorageBlockSize, kMaxAdvertisedShaderStorageBlockSize);
|
||||
if (m_vulkanCaps.SupportsShaderSubgroup) {
|
||||
|
||||
@@ -2458,6 +2458,33 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
}
|
||||
|
||||
// A 64-bit vertex input has to arrive as its 32-bit word pair: VK_FORMAT_R64*_SFLOAT is
|
||||
// optional and lavapipe advertises none of them at all. The pass is unconditional so it
|
||||
// always agrees with the Float64 case in VertexInputStateFactory::ToVkVertexFormat, and
|
||||
// ReflectVertexInputs below then sees an ordinary uvec2/uvec4 input.
|
||||
//
|
||||
// Failure here is not recoverable and must not be swallowed: ToVkVertexFormat has already
|
||||
// committed to R32G32{,B32A32}_UINT for the attribute, so a module still declaring
|
||||
// `in double` would reconcile to Unknown and build a pipeline with a UINT format under a
|
||||
// double input - garbage with no diagnostic anywhere.
|
||||
if (shaders[i] && shaders[i]->GetShaderStage() == ShaderStage::Vertex) {
|
||||
Vector<Uint> packedSpirv;
|
||||
const Bool packOk = MG_Util::ShaderTranspiler::ShaderCompiler::PackDoubleVertexInputsForVulkan(
|
||||
moduleSpirvs[i], packedSpirv);
|
||||
MOBILEGL_ASSERT(packOk,
|
||||
"ProgramFactory: 64-bit vertex input packing failed for program %u; the "
|
||||
"vertex-input format and the shader input type now disagree",
|
||||
program.GetExternalIndex());
|
||||
if (packOk) {
|
||||
moduleSpirvs[i] = std::move(packedSpirv);
|
||||
} else {
|
||||
MGLOG_E("ProgramFactory: failed to pack 64-bit vertex inputs for program %u; "
|
||||
"double-typed vertex attributes will be fetched as uint32 words and not "
|
||||
"reinterpreted",
|
||||
program.GetExternalIndex());
|
||||
}
|
||||
}
|
||||
|
||||
// When Vulkan can legally access storage images without a statically declared
|
||||
// format, let GL's glBindImageTexture format select the runtime image view. This
|
||||
// provides desktop-driver-compatible behavior for packs such as iterationRP, whose
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Stride, sizeof(attr.Stride)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Offset, sizeof(attr.Offset)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsInteger, sizeof(attr.IsInteger)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsLong, sizeof(attr.IsLong)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.IsBgra, sizeof(attr.IsBgra)));
|
||||
XXHASH_VERIFY(XXH64_update(m_hashState, &attr.Divisor, sizeof(attr.Divisor)));
|
||||
|
||||
@@ -97,7 +98,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
const VkFormat sourceVkFormat =
|
||||
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra);
|
||||
ToVkVertexFormat(attr.Type, attr.Size, attr.Normalized, attr.IsInteger, attr.IsBgra, attr.IsLong);
|
||||
if (sourceVkFormat == VK_FORMAT_UNDEFINED) {
|
||||
MGLOG_E("Unsupported vertex attribute layout (location=%u, type=%s, size=%d): the array is "
|
||||
"enabled but cannot be mapped to a VkFormat",
|
||||
@@ -273,7 +274,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
}
|
||||
|
||||
VkFormat VertexInputStateFactory::ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger,
|
||||
Bool isBgra) {
|
||||
Bool isBgra, Bool isLong) {
|
||||
if (isBgra) {
|
||||
// GL_BGRA: four reversed-order components, always normalized (enforced at validation), only
|
||||
// legal with GL_UNSIGNED_BYTE or a 2_10_10_10 type. The reversed VkFormats put the
|
||||
@@ -298,6 +299,22 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
case DataType::Int2101010Rev:
|
||||
if (isInteger || size != 4) return VK_FORMAT_UNDEFINED;
|
||||
return normalized ? VK_FORMAT_A2B10G10R10_SNORM_PACK32 : VK_FORMAT_A2B10G10R10_SSCALED_PACK32;
|
||||
case DataType::Float64:
|
||||
// A 64-bit attribute is fetched as its 32-bit word pair and bitcast back to double in the
|
||||
// shader (PackDoubleVertexInputsPass does the shader half). That is bit-exact and, unlike
|
||||
// VK_FORMAT_R64*_SFLOAT, needs no format capability: lavapipe reports bufferFeatures = 0
|
||||
// for every R64 float format, so a native 64-bit vertex fetch is simply unavailable there
|
||||
// while shaderFloat64 is not. Both halves key off nothing but the attribute being long,
|
||||
// so they always agree without extra plumbing.
|
||||
if (!isLong || isInteger || normalized) return VK_FORMAT_UNDEFINED;
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R32G32_UINT;
|
||||
case 2: return VK_FORMAT_R32G32B32A32_UINT;
|
||||
// A dvec3/dvec4 input is 6/8 uint32 components: no single VkFormat, and GL spreads it
|
||||
// over two attribute locations, which the location-per-VAO-index model here does not
|
||||
// express. Declined rather than fetched wrong.
|
||||
default: return VK_FORMAT_UNDEFINED;
|
||||
}
|
||||
case DataType::Float32:
|
||||
switch (size) {
|
||||
case 1: return VK_FORMAT_R32_SFLOAT;
|
||||
|
||||
@@ -93,7 +93,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
static SizeT GetAttributeByteSize(DataType type, Int size, Bool isBgra);
|
||||
|
||||
private:
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false);
|
||||
static VkFormat ToVkVertexFormat(DataType type, Int size, Bool normalized, Bool isInteger, Bool isBgra = false,
|
||||
Bool isLong = false);
|
||||
static Bool IsScaledIntegerVertexFormat(VkFormat format);
|
||||
static VkFormat ToFloat32VertexFormat(Int componentCount);
|
||||
Bool SupportsVertexBufferFormat(VkFormat format) const;
|
||||
|
||||
@@ -9925,6 +9925,12 @@ void main() {
|
||||
deviceFeatures.wideLines = supportedDeviceFeatures.wideLines;
|
||||
m_logicOpFeatureEnabled = deviceFeatures.logicOp == VK_TRUE;
|
||||
deviceFeatures.shaderInt64 = supportedDeviceFeatures.shaderInt64;
|
||||
// Required for any module that declares OpCapability Float64 - which is every shader with a
|
||||
// double in it, including the 64-bit vertex attribute path (the attribute itself arrives as
|
||||
// uint32 words, but the bitcast result and everything computed from it is Float64). Without
|
||||
// it vkCreateShaderModule is invalid usage (VUID-VkShaderModuleCreateInfo-pCode-08740),
|
||||
// which is why SupportsFloat64VertexAttributes gates the entry point on the same feature.
|
||||
deviceFeatures.shaderFloat64 = supportedDeviceFeatures.shaderFloat64;
|
||||
// Required for desktop GL image load/store semantics. iterationRP writes storage
|
||||
// images from vertex and fragment stages and uses formats outside Vulkan's small
|
||||
// mandatory storage-image set.
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include "GL_VertexArray.h"
|
||||
#include "Validators.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
#include <MG_Impl/GLImpl/Buffer/Validators.h>
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_State/GLState/ErrorState/Error.h>
|
||||
@@ -173,6 +174,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_CURRENT_VERTEX_ATTRIB:
|
||||
case GL_VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
|
||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||
// Core since GL 4.1 (ARB_vertex_attrib_64bit). It was rejected while no attribute could
|
||||
// ever be long; now that IsLong is real state the pname has to be accepted.
|
||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||
case GL_VERTEX_ATTRIB_ARRAY_POINTER:
|
||||
return true;
|
||||
@@ -460,19 +464,38 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
relativeoffset, isBgra);
|
||||
}
|
||||
|
||||
// The long (64-bit) attribute format. MobileGL has no 64-bit vertex attributes, so nothing is
|
||||
// recorded; what the entry point owes the application is the parameter validation, which is
|
||||
// observable through glGetError regardless of whether the format could be used in a draw.
|
||||
static void VertexAttribLFormatSeparate_State(GLuint attribindex, GLint size, GLenum type,
|
||||
// The long (64-bit) attribute format: the values reach the shader as doubles, unconverted
|
||||
// (GL 4.6 core 10.3.2). ValidateVertexAttribLFormat has already pinned type to GL_DOUBLE, so the
|
||||
// recorded DataType is always Float64 - what IsLong adds is that this is the *unconverted* form,
|
||||
// as opposed to VertexAttribFormat(GL_DOUBLE), which asks for a float conversion.
|
||||
//
|
||||
// Whether the backend can feed it is detected, not assumed: DirectVulkan needs shaderFloat64,
|
||||
// and DirectGLES can never have it at all. A backend without it declines here, loudly - GL error
|
||||
// plus a log line naming the reason - rather than accepting state no draw could honour and
|
||||
// rendering garbage. The matching startup POST row is in MG_Util/SelfTest/DriverPost.cpp.
|
||||
static void VertexAttribLFormatSeparate_State(const SharedPtr<MG_State::GLState::VertexArrayObject>& vao,
|
||||
GLuint attribindex, GLint size, GLenum type,
|
||||
GLuint relativeoffset) {
|
||||
if (!VertexArrayImpl::ValidateVertexAttributeIndex(attribindex)) return;
|
||||
if (!VertexArrayImpl::ValidateVertexAttribLFormat(attribindex, size, type)) return;
|
||||
if (!VertexArrayImpl::ValidateVertexAttribRelativeOffset(relativeoffset)) return;
|
||||
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
|
||||
"64-bit vertex attributes are not supported."));
|
||||
if (!MG_Backend::pActiveBackendObject ||
|
||||
!MG_Backend::pActiveBackendObject->GetDynamicParameters().SupportsFloat64VertexAttributes) {
|
||||
MGLOG_I("VertexAttribLFormat: attribute %u asked for a 64-bit (GL_DOUBLE) format, but this "
|
||||
"backend has no double-precision vertex attribute support - see the "
|
||||
"\"64-bit vertex attributes\" / \"shaderFloat64\" POST row for what that costs",
|
||||
attribindex);
|
||||
MG_State::pGLContext->RecordError(
|
||||
ErrorCode::InvalidOperation,
|
||||
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", "VertexAttribLFormat",
|
||||
"64-bit vertex attributes are not supported by this backend."));
|
||||
return;
|
||||
}
|
||||
|
||||
vao->SetAttributeFormatSeparate(attribindex, size, MG_Util::ConvertGLEnumToDataType(type),
|
||||
/*normalized: */ false, /*isInteger: */ false, relativeoffset,
|
||||
/*isBgra: */ false, /*isLong: */ true);
|
||||
}
|
||||
|
||||
void VertexArrayAttribFormat_State(GLuint vaobj, GLuint attribindex, GLint size, GLenum type,
|
||||
@@ -915,6 +938,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||
params[0] = attr->IsInteger ? 1.0f : 0.0f;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
||||
params[0] = attr->IsLong ? 1.0f : 0.0f;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||
params[0] = static_cast<GLfloat>(attr->Divisor);
|
||||
return;
|
||||
@@ -975,6 +1001,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||
params[0] = attr->IsInteger ? 1.0 : 0.0;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
||||
params[0] = attr->IsLong ? 1.0 : 0.0;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||
params[0] = static_cast<GLdouble>(attr->Divisor);
|
||||
return;
|
||||
@@ -1031,6 +1060,9 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
case GL_VERTEX_ATTRIB_ARRAY_INTEGER:
|
||||
params[0] = attr->IsInteger ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
||||
params[0] = attr->IsLong ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||
params[0] = static_cast<GLint>(attr->Divisor);
|
||||
return;
|
||||
@@ -1164,8 +1196,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
*param = attr.IsInteger ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_LONG:
|
||||
// 64-bit attributes are not supported, so no attribute is ever a long one.
|
||||
*param = GL_FALSE;
|
||||
*param = attr.IsLong ? GL_TRUE : GL_FALSE;
|
||||
return;
|
||||
case GL_VERTEX_ATTRIB_ARRAY_DIVISOR:
|
||||
*param = static_cast<GLint>(attr.Divisor);
|
||||
@@ -1259,13 +1290,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void VertexAttribLFormat(GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
||||
auto vao = GetBoundVertexArrayOrError("VertexAttribLFormat");
|
||||
if (!vao) return;
|
||||
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
|
||||
VertexAttribLFormatSeparate_State(vao, attribindex, size, type, relativeoffset);
|
||||
}
|
||||
|
||||
void VertexArrayAttribLFormat(GLuint vaobj, GLuint attribindex, GLint size, GLenum type, GLuint relativeoffset) {
|
||||
auto vao = GetNamedVertexArrayObject_State(vaobj, "VertexArrayAttribLFormat");
|
||||
if (!vao) return;
|
||||
VertexAttribLFormatSeparate_State(attribindex, size, type, relativeoffset);
|
||||
VertexAttribLFormatSeparate_State(vao, attribindex, size, type, relativeoffset);
|
||||
}
|
||||
|
||||
void VertexAttribBinding(GLuint attribindex, GLuint bindingindex) {
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (m_attributes[index].Size == size && m_attributes[index].Type == type &&
|
||||
m_attributes[index].Normalized == normalized && m_attributes[index].Stride == stride &&
|
||||
m_attributes[index].Offset == offset && m_attributes[index].IsInteger == isInteger &&
|
||||
m_attributes[index].IsBgra == isBgra) {
|
||||
m_attributes[index].IsBgra == isBgra && !m_attributes[index].IsLong) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -73,6 +73,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
attr.Offset = offset;
|
||||
attr.IsInteger = isInteger;
|
||||
attr.IsBgra = isBgra;
|
||||
// glVertexAttribPointer / glVertexAttribIPointer are never the long form, so they always
|
||||
// take the attribute back out of it - and "only IsLong changed" is a real change that has to
|
||||
// reach the backends, which is why the early-out above tests it too. Cleared inside the
|
||||
// mutation block so the clear and the version bump stay atomic.
|
||||
attr.IsLong = false;
|
||||
|
||||
BumpAttributeFormatVersion(index);
|
||||
}
|
||||
@@ -212,18 +217,21 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
void VertexArrayObject::SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
||||
Bool isInteger, Uint relativeOffset, Bool isBgra) {
|
||||
Bool isInteger, Uint relativeOffset, Bool isBgra,
|
||||
Bool isLong) {
|
||||
if (attribIndex >= MAX_VERTEX_ATTRIBS) return;
|
||||
if (size < 1 || size > 4) return;
|
||||
|
||||
auto& attr = m_attributes[attribIndex];
|
||||
if (attr.Size != size || attr.Type != type || attr.Normalized != normalized || attr.IsInteger != isInteger ||
|
||||
attr.IsBgra != isBgra || m_attributeRelativeOffset[attribIndex] != relativeOffset) {
|
||||
attr.IsBgra != isBgra || attr.IsLong != isLong ||
|
||||
m_attributeRelativeOffset[attribIndex] != relativeOffset) {
|
||||
attr.Size = size;
|
||||
attr.Type = type;
|
||||
attr.Normalized = normalized;
|
||||
attr.IsInteger = isInteger;
|
||||
attr.IsBgra = isBgra;
|
||||
attr.IsLong = isLong;
|
||||
m_attributeRelativeOffset[attribIndex] = relativeOffset;
|
||||
BumpAttributeFormatVersion(attribIndex);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,12 @@ namespace MobileGL {
|
||||
SizeT Offset = 0;
|
||||
Bool IsInteger = false;
|
||||
// GL_BGRA vertex size: four components in reversed (B,G,R,A) memory order. Size stays 4.
|
||||
// Set only by the long (L) format entry points. It is NOT implied by
|
||||
// Type == Float64: VertexAttribFormat(GL_DOUBLE) also reads doubles from memory but
|
||||
// asks for them *converted to float*, while VertexAttribLFormat keeps all 64 bits
|
||||
// (GL 4.6 core 10.3.2). Backends have to tell the two apart, and it is what
|
||||
// GL_VERTEX_ATTRIB_ARRAY_LONG reports.
|
||||
Bool IsLong = false;
|
||||
Bool IsBgra = false;
|
||||
Uint Divisor = 0;
|
||||
SharedPtr<BufferObject> Buffer;
|
||||
@@ -88,7 +94,8 @@ namespace MobileGL {
|
||||
void SetBindingDivisor(Uint bindingIndex, Uint divisor);
|
||||
void SetAttributeBinding(Uint attribIndex, Uint bindingIndex);
|
||||
void SetAttributeFormatSeparate(Uint attribIndex, int size, DataType type, Bool normalized,
|
||||
Bool isInteger, Uint relativeOffset, Bool isBgra = false);
|
||||
Bool isInteger, Uint relativeOffset, Bool isBgra = false,
|
||||
Bool isLong = false);
|
||||
|
||||
// The binding-point view the attributes were resolved from. Kept queryable
|
||||
// because glGetVertexArrayIndexed[64]iv reports it verbatim, and the resolved
|
||||
|
||||
@@ -198,6 +198,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
VkPhysicalDeviceFeatures supportedFeatures{};
|
||||
vkGetPhysicalDeviceFeatures(physicalDevice, &supportedFeatures);
|
||||
caps.SupportsWideLines = supportedFeatures.wideLines == VK_TRUE;
|
||||
caps.SupportsShaderFloat64 = supportedFeatures.shaderFloat64 == VK_TRUE;
|
||||
caps.SupportsVertexPipelineStoresAndAtomics =
|
||||
supportedFeatures.vertexPipelineStoresAndAtomics == VK_TRUE;
|
||||
caps.SupportsFragmentStoresAndAtomics = supportedFeatures.fragmentStoresAndAtomics == VK_TRUE;
|
||||
@@ -288,6 +289,7 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
caps.ViewportSubpixelBits = static_cast<Int>(properties.limits.viewportSubPixelBits);
|
||||
FillFragmentInterpolationLimits(caps, properties.limits);
|
||||
caps.SupportsWideLines = false;
|
||||
caps.SupportsShaderFloat64 = false;
|
||||
// This helper only receives properties, not VkPhysicalDeviceFeatures. Leave optional
|
||||
// stage writes disabled rather than inferring them from descriptor limits alone.
|
||||
caps.SupportsVertexPipelineStoresAndAtomics = false;
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace MobileGL {
|
||||
Float MaxFragmentInterpolationOffset = 0.4375f;
|
||||
Int FragmentInterpolationOffsetBits = 4;
|
||||
Bool SupportsWideLines = false;
|
||||
// VkPhysicalDeviceFeatures::shaderFloat64. Any module declaring OpCapability Float64
|
||||
// needs it, which includes every 64-bit vertex attribute: the attribute itself arrives
|
||||
// as 32-bit words, but the bitcast result and everything computed from it is Float64.
|
||||
Bool SupportsShaderFloat64 = 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.
|
||||
|
||||
@@ -319,6 +319,14 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
"map array texture gets no driver storage at all, so sampling one reads nothing "
|
||||
"and rendering to one does not reach the screen");
|
||||
}
|
||||
// Reported rather than probed: this one cannot come out any other way. OpenGL ES has no
|
||||
// double-precision vertex format and ESSL has no fp64 type, so there is no driver and no
|
||||
// extension that could make it work - the row exists so the loss is named at startup
|
||||
// instead of discovered as an unexplained GL_INVALID_OPERATION at draw setup.
|
||||
builder.Warn("64-bit vertex attributes",
|
||||
"not supported on any GLES driver (ES has no GL_DOUBLE vertex format and ESSL has "
|
||||
"no fp64 type); glVertexAttribLFormat / glVertexArrayAttribLFormat report "
|
||||
"GL_INVALID_OPERATION - use the Vulkan backend if the application needs them");
|
||||
if (glesFuncs.glPatchParameteri != nullptr) {
|
||||
builder.Pass("Tessellation patch parameters",
|
||||
"glPatchParameteri present (GL_PATCH_VERTICES reaches the driver)");
|
||||
@@ -1411,6 +1419,14 @@ namespace MobileGL::MG_Util::SelfTest {
|
||||
} else {
|
||||
builder.Warn("dualSrcBlend", "unsupported; GL_SRC1_* dual-source blend factors hard-fail at draw");
|
||||
}
|
||||
if (features.shaderFloat64 == VK_TRUE) {
|
||||
builder.Pass("shaderFloat64",
|
||||
"GLSL double/dvec/dmat and 64-bit vertex attributes (glVertexAttribLFormat) supported");
|
||||
} else {
|
||||
builder.Warn("shaderFloat64",
|
||||
"unsupported; any shader declaring a double fails to create a shader module, and "
|
||||
"glVertexAttribLFormat reports GL_INVALID_OPERATION instead of feeding the attribute");
|
||||
}
|
||||
|
||||
Bool shaderDrawParameters = false;
|
||||
if (vkGetPhysicalDeviceFeatures2Fn != nullptr && properties.apiVersion >= VK_API_VERSION_1_1) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "SpirvPasses/DecomposeWorkgroupVec3Pass.h"
|
||||
#include "SpirvPasses/DecoratePositionInvariantPass.h"
|
||||
#include "SpirvPasses/LowerDrawParametersPass.h"
|
||||
#include "SpirvPasses/PackDoubleVertexInputsPass.h"
|
||||
#include "SpirvPasses/RebaseInstanceIndexPass.h"
|
||||
#include "SpirvPasses/NormalizeRectCoordinatesPass.h"
|
||||
#include "SpirvPasses/StripUboMemberRelaxedPrecisionPass.h"
|
||||
@@ -327,6 +328,18 @@ namespace MobileGL {
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
using namespace spvtools;
|
||||
OptimizerOptions options;
|
||||
options.set_run_validator(false);
|
||||
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass());
|
||||
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::StripUboMemberRelaxedPrecisionForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
using namespace spvtools;
|
||||
|
||||
@@ -54,6 +54,13 @@ namespace MobileGL {
|
||||
static bool LowerRectImages(const Vector<Uint32>& inputBinary, Vector<uint32_t>& outputBinary);
|
||||
static bool RebaseInstanceIndexForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
// Re-declares 64-bit float vertex inputs as their 32-bit unsigned word pair
|
||||
// (double -> uvec2, dvec2 -> uvec4) and bitcasts them back to double at entry, so no
|
||||
// VK_FORMAT_R64*_SFLOAT is needed - lavapipe advertises none of them for vertex
|
||||
// buffers. Vertex stage, DirectVulkan only; pairs with the Float64 case in
|
||||
// VertexInputStateFactory::ToVkVertexFormat.
|
||||
static bool PackDoubleVertexInputsForVulkan(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
// Adds the Invariant decoration to every Position builtin output. GL apps
|
||||
// routinely rely on cross-program position invariance for multi-pass
|
||||
// equality depth tests (e.g. GEQUAL re-draws of the same geometry), and
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "PackDoubleVertexInputsPass.h"
|
||||
|
||||
#include "spirv.hpp"
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/module.h"
|
||||
#include "source/opt/types.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
using spvtools::opt::IRContext;
|
||||
using spvtools::opt::Instruction;
|
||||
using spvtools::opt::Operand;
|
||||
namespace analysis = spvtools::opt::analysis;
|
||||
|
||||
// Component count of a 64-bit float input, or 0 if the type is not one.
|
||||
Uint32 DoubleComponentCount(const analysis::Type* type) {
|
||||
if (type == nullptr) return 0;
|
||||
if (const auto* scalar = type->AsFloat()) {
|
||||
return scalar->width() == 64 ? 1u : 0u;
|
||||
}
|
||||
if (const auto* vector = type->AsVector()) {
|
||||
const auto* element = vector->element_type()->AsFloat();
|
||||
if (element == nullptr || element->width() != 64) return 0;
|
||||
return vector->element_count();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
spvtools::opt::Pass::Status PackDoubleVertexInputsPass::Process() {
|
||||
auto* irContext = context();
|
||||
auto entryPoints = irContext->module()->entry_points();
|
||||
if (entryPoints.begin() == entryPoints.end()) return Status::SuccessWithoutChange;
|
||||
|
||||
Instruction* entryPoint = &*entryPoints.begin();
|
||||
if (static_cast<spv::ExecutionModel>(entryPoint->GetSingleWordInOperand(0)) !=
|
||||
spv::ExecutionModel::Vertex) {
|
||||
return Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
auto* defUseMgr = irContext->get_def_use_mgr();
|
||||
auto* typeMgr = irContext->get_type_mgr();
|
||||
|
||||
struct Target {
|
||||
Instruction* variable = nullptr;
|
||||
Uint32 doubleTypeId = 0;
|
||||
Uint32 componentCount = 0;
|
||||
Uint32 packedTypeId = 0;
|
||||
Uint32 packedPointerTypeId = 0;
|
||||
Uint32 privatePointerTypeId = 0;
|
||||
};
|
||||
std::vector<Target> targets;
|
||||
|
||||
for (Instruction& inst : irContext->types_values()) {
|
||||
if (inst.opcode() != spv::Op::OpVariable) continue;
|
||||
if (static_cast<spv::StorageClass>(inst.GetSingleWordInOperand(0)) !=
|
||||
spv::StorageClass::Input) {
|
||||
continue;
|
||||
}
|
||||
Instruction* pointerType = defUseMgr->GetDef(inst.type_id());
|
||||
if (pointerType == nullptr) continue;
|
||||
const Uint32 pointeeTypeId = pointerType->GetSingleWordInOperand(1);
|
||||
const Uint32 components = DoubleComponentCount(typeMgr->GetType(pointeeTypeId));
|
||||
if (components == 0) continue;
|
||||
if (components > 2) {
|
||||
// 6 or 8 uint32 components has no single vertex format, and GL spreads such
|
||||
// an input over two attribute locations. Left alone; the vertex-input
|
||||
// factory declines the matching attribute for the same reason.
|
||||
MGLOG_E("PackDoubleVertexInputsPass: vertex input %%%u is a %u-component 64-bit "
|
||||
"float; only double and dvec2 inputs can be packed",
|
||||
inst.result_id(), components);
|
||||
continue;
|
||||
}
|
||||
targets.push_back({&inst, pointeeTypeId, components});
|
||||
}
|
||||
|
||||
if (targets.empty()) return Status::SuccessWithoutChange;
|
||||
|
||||
// Entry block insertion point: after the block's leading OpVariable run, which
|
||||
// SPIR-V requires to stay at the top of a function's first block.
|
||||
const Uint32 entryFunctionId = entryPoint->GetSingleWordInOperand(1);
|
||||
spvtools::opt::Function* entryFunction = nullptr;
|
||||
for (auto& function : *irContext->module()) {
|
||||
if (function.result_id() == entryFunctionId) {
|
||||
entryFunction = &function;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (entryFunction == nullptr || entryFunction->begin() == entryFunction->end()) {
|
||||
return Status::SuccessWithoutChange;
|
||||
}
|
||||
auto& entryBlock = *entryFunction->begin();
|
||||
auto insertPoint = entryBlock.begin();
|
||||
while (insertPoint != entryBlock.end() && insertPoint->opcode() == spv::Op::OpVariable) {
|
||||
++insertPoint;
|
||||
}
|
||||
if (insertPoint == entryBlock.end()) return Status::SuccessWithoutChange;
|
||||
|
||||
const Uint32 uintTypeId = typeMgr->GetUIntTypeId();
|
||||
const analysis::Integer* uintType = typeMgr->GetType(uintTypeId)->AsInteger();
|
||||
|
||||
// Every type instruction has to exist before any variable that names it: the
|
||||
// types-and-variables section is walked in order and a forward reference to a type is
|
||||
// invalid SPIR-V. GetTypeInstruction/FindPointerToType append, and the variables are
|
||||
// appended (or re-appended) below, so all three lookups run first for every target.
|
||||
for (auto& target : targets) {
|
||||
analysis::Vector packedVectorType(uintType, target.componentCount * 2u);
|
||||
target.packedTypeId = typeMgr->GetTypeInstruction(&packedVectorType);
|
||||
target.packedPointerTypeId =
|
||||
typeMgr->FindPointerToType(target.packedTypeId, spv::StorageClass::Input);
|
||||
target.privatePointerTypeId =
|
||||
typeMgr->FindPointerToType(target.doubleTypeId, spv::StorageClass::Private);
|
||||
}
|
||||
|
||||
for (const auto& target : targets) {
|
||||
Instruction* variable = target.variable;
|
||||
const Uint32 oldVariableId = variable->result_id();
|
||||
const Uint32 packedTypeId = target.packedTypeId;
|
||||
const Uint32 packedPointerTypeId = target.packedPointerTypeId;
|
||||
|
||||
const Uint32 packedVariableId = irContext->TakeNextId();
|
||||
irContext->AddGlobalValue(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpVariable, packedPointerTypeId, packedVariableId,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_STORAGE_CLASS,
|
||||
{static_cast<Uint32>(spv::StorageClass::Input)}}}));
|
||||
|
||||
// The interface decorations belong to whatever is actually the Input now.
|
||||
std::vector<Instruction*> deadDecorations;
|
||||
for (auto& annotation : irContext->annotations()) {
|
||||
if (annotation.opcode() != spv::Op::OpDecorate) continue;
|
||||
if (annotation.GetSingleWordInOperand(0) != oldVariableId) continue;
|
||||
const auto decoration =
|
||||
static_cast<spv::Decoration>(annotation.GetSingleWordInOperand(1));
|
||||
if (decoration == spv::Decoration::Location ||
|
||||
decoration == spv::Decoration::Component ||
|
||||
decoration == spv::Decoration::RelaxedPrecision) {
|
||||
annotation.SetInOperand(0, {packedVariableId});
|
||||
} else {
|
||||
deadDecorations.push_back(&annotation);
|
||||
}
|
||||
}
|
||||
for (auto* annotation : deadDecorations) {
|
||||
irContext->KillInst(annotation);
|
||||
}
|
||||
|
||||
// Demote the original to a Private global: every existing OpLoad /
|
||||
// OpAccessChain on it stays valid and keeps its double type. It is also moved to
|
||||
// the end of the section, because the pointer-to-Private type it now names was
|
||||
// appended above and a variable may not forward-reference its own type.
|
||||
variable->SetResultType(target.privatePointerTypeId);
|
||||
variable->SetInOperand(0, {static_cast<Uint32>(spv::StorageClass::Private)});
|
||||
variable->RemoveFromList();
|
||||
irContext->AddGlobalValue(std::unique_ptr<Instruction>(variable));
|
||||
|
||||
// SPIR-V 1.3 lists only Input/Output in the entry-point interface.
|
||||
std::vector<Operand> interfaceOperands;
|
||||
for (Uint32 i = 0; i < entryPoint->NumInOperands(); ++i) {
|
||||
const Operand& operand = entryPoint->GetInOperand(i);
|
||||
if (i >= 3 && operand.type == SPV_OPERAND_TYPE_ID &&
|
||||
entryPoint->GetSingleWordInOperand(i) == oldVariableId) {
|
||||
interfaceOperands.push_back({SPV_OPERAND_TYPE_ID, {packedVariableId}});
|
||||
continue;
|
||||
}
|
||||
interfaceOperands.push_back(operand);
|
||||
}
|
||||
entryPoint->SetInOperands(std::move(interfaceOperands));
|
||||
|
||||
const Uint32 loadedId = irContext->TakeNextId();
|
||||
const Uint32 bitcastId = irContext->TakeNextId();
|
||||
insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpLoad, packedTypeId, loadedId,
|
||||
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {packedVariableId}}}));
|
||||
++insertPoint;
|
||||
insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpBitcast, target.doubleTypeId, bitcastId,
|
||||
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {loadedId}}}));
|
||||
++insertPoint;
|
||||
insertPoint = insertPoint.InsertBefore(spvtools::MakeUnique<Instruction>(
|
||||
irContext, spv::Op::OpStore, 0, 0,
|
||||
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {oldVariableId}},
|
||||
{SPV_OPERAND_TYPE_ID, {bitcastId}}}));
|
||||
++insertPoint;
|
||||
}
|
||||
|
||||
irContext->InvalidateAnalysesExceptFor(IRContext::kAnalysisNone);
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
spvtools::Optimizer::PassToken PackDoubleVertexInputsPass::CreatePackDoubleVertexInputsPass() {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<PackDoubleVertexInputsPass>());
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -0,0 +1,43 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/PackDoubleVertexInputsPass.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include "source/opt/pass.h"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
// Re-declares every 64-bit floating-point *vertex input* as the 32-bit unsigned word
|
||||
// pair that holds the same bytes (double -> uvec2, dvec2 -> uvec4), demotes the
|
||||
// original variable to a Private global, and seeds it once at the top of the entry
|
||||
// point with an OpBitcast of the new input. Everything downstream keeps loading the
|
||||
// same id and the same double type, so no other instruction is rewritten.
|
||||
//
|
||||
// Why not just use VK_FORMAT_R64*_SFLOAT: those formats are optional, and lavapipe
|
||||
// reports zero bufferFeatures for all four of them, so a 64-bit vertex fetch is
|
||||
// impossible there even though shaderFloat64 is supported. The word-pair form needs no
|
||||
// format capability at all and is bit-exact, so it is applied unconditionally rather
|
||||
// than as a fallback - which also keeps it in lockstep with
|
||||
// VertexInputStateFactory::ToVkVertexFormat, since both branch on nothing but "is this
|
||||
// a 64-bit vertex input".
|
||||
//
|
||||
// DirectVulkan only. The 64-bit *arithmetic* still needs the Float64 capability, i.e.
|
||||
// an enabled VkPhysicalDeviceFeatures::shaderFloat64.
|
||||
class PackDoubleVertexInputsPass : public spvtools::opt::Pass {
|
||||
public:
|
||||
const char* name() const override { return "mobilegl-pack-double-vertex-inputs"; }
|
||||
Status Process() override;
|
||||
|
||||
static spvtools::Optimizer::PassToken CreatePackDoubleVertexInputsPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
Reference in New Issue
Block a user