From 8c2d5019c26082efa459251787d035b026cf96ee Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 12 Oct 2026 21:13:30 -0400 Subject: [PATCH] [Fix] (Magma): convert 32-bit int and packed 2_10_10_10 float attributes on the CPU --- .../Renderer/VertexInputStateFactory.cpp | 14 ++++++++ .../DirectVulkan/Renderer/WireDraw.inc | 35 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp index 83bf620b..e7a7c11a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.cpp @@ -160,6 +160,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { format = ToFloat32VertexFormat(attr.Size); conversion = VertexStreamConversion::Float64ToFloat32; } + // GL_INT / GL_UNSIGNED_INT read as float (glVertexAttribFormat without the I suffix): + // Vulkan has no 32-bit SCALED or NORM vertex format, so the stream is converted to + // float32 on the CPU, normalized when the attribute asks for it. + if (format == VK_FORMAT_UNDEFINED && !attr.IsInteger && !attr.IsLong && !attr.IsBgra && + (type == DataType::Int32 || type == DataType::Uint32)) { + format = ToFloat32VertexFormat(attr.Size); + conversion = VertexStreamConversion::ScaledIntegerToFloat32; + } if (format == VK_FORMAT_UNDEFINED) { maskOut("format-map"); continue; } if (!SupportsVertexBufferFormat(format)) { if (!IsScaledIntegerVertexFormat(format)) { maskOut("native-format-feature", format); continue; } @@ -432,6 +440,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { case VK_FORMAT_R16G16B16_SSCALED: case VK_FORMAT_R16G16B16A16_USCALED: case VK_FORMAT_R16G16B16A16_SSCALED: + // Packed 2_10_10_10 fetches are optional too (Adreno has neither SCALED form); the + // converter decodes the fields itself, normalized or not. + case VK_FORMAT_A2B10G10R10_USCALED_PACK32: + case VK_FORMAT_A2B10G10R10_SSCALED_PACK32: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + case VK_FORMAT_A2B10G10R10_SNORM_PACK32: return true; default: return false; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc index 3358f17f..ef762a3c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc @@ -86,18 +86,51 @@ return true; } output.resize(count * attr.Size * sizeof(Float)); + // GL 4.6 core 10.2.1 (equations 2.1 / 2.2) for a normalized fixed-point component; a + // double is not fixed-point, so its normalized flag changes nothing. + const Bool normalize = attr.Normalized && type != DataType::Float64; const auto convert = [&]() { for (SizeT i = 0; i < count; ++i) { for (SizeT component = 0; component < attr.Size; ++component) { T value{}; Memcpy(&value, source + i * stride + component * sizeof(T), sizeof(T)); - const Float converted = static_cast(value); + Float converted = static_cast(value); + if constexpr (std::is_integral_v) { + if (normalize) { + constexpr double kMax = static_cast(std::numeric_limits::max()); + converted = static_cast(std::is_signed_v + ? std::max(static_cast(value) / kMax, -1.0) + : static_cast(value) / kMax); + } + } Memcpy(output.data() + (i * attr.Size + component) * sizeof(Float), &converted, sizeof(Float)); } } return true; }; switch (type) { + case DataType::Uint2101010Rev: + case DataType::Int2101010Rev: { + // x, y, z in 10-bit fields from bit 0, w in the top 2 bits; GL 4.6 core 10.2.1. + if (attr.Size != 4 || attr.IsBgra) return false; + const Bool isSigned = type == DataType::Int2101010Rev; + for (SizeT i = 0; i < count; ++i) { + Uint32 word = 0; + Memcpy(&word, source + i * stride, sizeof(word)); + for (Uint32 component = 0; component < 4; ++component) { + const Uint32 bits = component < 3 ? 10u : 2u; + const Uint32 field = (word >> (component * 10u)) & ((1u << bits) - 1u); + const Int32 value = isSigned && (field >> (bits - 1u)) + ? static_cast(field) - static_cast(1u << bits) : static_cast(field); + Float converted = static_cast(value); + if (normalize) + converted = isSigned ? std::max(converted / static_cast((1u << (bits - 1u)) - 1u), -1.0f) + : converted / static_cast((1u << bits) - 1u); + Memcpy(output.data() + (i * 4 + component) * sizeof(Float), &converted, sizeof(Float)); + } + } + return true; + } case DataType::Float64: return convert.template operator()(); case DataType::Int8: return convert.template operator()(); case DataType::Uint8: return convert.template operator()();