mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (MG_Util, DirectVulkan): canonical depth shadows with upload conversion
Depth textures previously raw-copied whatever the client handed over into the Vulkan image, so any client format other than the image's exact texel layout uploaded garbage (float DEPTH_COMPONENT data read as 16-bit words, GL_TEXTURE_1D/2D alike). The shadow now has a defined canonical layout - unorm16 for DEPTH_COMPONENT16, a full-scale unorm32 word for the 24/32-bit fixed depths, float for DEPTH_COMPONENT32F - produced by the pixel-store unpack converter (new DepthComponent channel mapping + UNorm32 component). GL_DEPTH_COMPONENT client data may also fill packed depth-stencil internals (stencil half zero). The Vulkan uploader converts shadow words to the image texel layout per aspect, and X8_D24_UNORM falls back to D32_SFLOAT where optimal tiling lacks support (lavapipe). texture_size_promotion.functional and packed_depth_stencil.verify_copy_tex_image.* now pass.
This commit is contained in:
@@ -830,7 +830,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return VK_NULL_HANDLE;
|
return VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (baseArrayLayer == 0 && layerCount == resource->arrayLayers && viewType == resource->viewType) {
|
const Bool framebufferSrgbEnabled =
|
||||||
|
MG_State::pGLContext->IsCapabilityEnabled(MobileGL::CapabilityInput::FramebufferSrgb);
|
||||||
|
const VkFormat attachmentFormat = ResolveSrgbAttachmentWriteFormat(resource->format, framebufferSrgbEnabled);
|
||||||
|
|
||||||
|
if (attachmentFormat == resource->format && baseArrayLayer == 0 && layerCount == resource->arrayLayers &&
|
||||||
|
viewType == resource->viewType) {
|
||||||
return GetOrCreateViewAtMipLevel(texture, mipLevel);
|
return GetOrCreateViewAtMipLevel(texture, mipLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -839,6 +844,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
.baseArrayLayer = baseArrayLayer,
|
.baseArrayLayer = baseArrayLayer,
|
||||||
.layerCount = layerCount,
|
.layerCount = layerCount,
|
||||||
.viewType = viewType,
|
.viewType = viewType,
|
||||||
|
.viewFormat = attachmentFormat,
|
||||||
};
|
};
|
||||||
auto it = resource->attachmentViews.find(key);
|
auto it = resource->attachmentViews.find(key);
|
||||||
if (it == resource->attachmentViews.end()) {
|
if (it == resource->attachmentViews.end()) {
|
||||||
@@ -849,7 +855,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
return attachmentView;
|
return attachmentView;
|
||||||
}
|
}
|
||||||
|
|
||||||
attachmentView = CreateImageView(resource->image, resource->format, resource->aspect, viewType,
|
attachmentView = CreateImageView(resource->image, attachmentFormat, resource->aspect, viewType,
|
||||||
mipLevel, 1, baseArrayLayer, layerCount);
|
mipLevel, 1, baseArrayLayer, layerCount);
|
||||||
if (attachmentView == VK_NULL_HANDLE) {
|
if (attachmentView == VK_NULL_HANDLE) {
|
||||||
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d",
|
MGLOG_D("%s: CreateImageView failed for textureId=%d mipLevel=%u baseArrayLayer=%u layerCount=%u viewType=%d",
|
||||||
@@ -1438,11 +1444,23 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
|
const IntVec3 &texelSize, SizeT byteSize, Uint32 mipLevels,
|
||||||
TextureResource &resource) {
|
TextureResource &resource) {
|
||||||
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
|
const TextureFormatInfo formatInfo = ResolveTextureFormatInfo(texture.GetFormat());
|
||||||
const VkFormat format = formatInfo.format;
|
VkFormat format = formatInfo.format;
|
||||||
if (format == VK_FORMAT_UNDEFINED) {
|
if (format == VK_FORMAT_UNDEFINED) {
|
||||||
MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__);
|
MGLOG_D("%s: format == VK_FORMAT_UNDEFINED", __func__);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// X8_D24 lacks optimal-tiling support on several drivers (lavapipe included);
|
||||||
|
// D32_SFLOAT holds every 24-bit depth value exactly, and the upload path
|
||||||
|
// converts the shadow words to float (see the pure-depth branch below).
|
||||||
|
if (format == VK_FORMAT_X8_D24_UNORM_PACK32) {
|
||||||
|
VkFormatProperties formatProperties{};
|
||||||
|
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, format, &formatProperties);
|
||||||
|
constexpr VkFormatFeatureFlags kDepthAttachmentAndSample =
|
||||||
|
VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT;
|
||||||
|
if ((formatProperties.optimalTilingFeatures & kDepthAttachmentAndSample) != kDepthAttachmentAndSample) {
|
||||||
|
format = VK_FORMAT_D32_SFLOAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) {
|
if (texelSize.x() <= 0 || texelSize.y() <= 0 /*|| byteSize == 0*/) {
|
||||||
MGLOG_D("%s: texelSize or byteSize is zero", __func__);
|
MGLOG_D("%s: texelSize or byteSize is zero", __func__);
|
||||||
return false;
|
return false;
|
||||||
@@ -1514,6 +1532,14 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
||||||
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||||
}
|
}
|
||||||
|
// sRGB color images attach through their UNORM twin while GL_FRAMEBUFFER_SRGB is
|
||||||
|
// disabled (see ResolveSrgbAttachmentWriteFormat), which needs format-reinterpreting
|
||||||
|
// views - multisample sRGB render targets included.
|
||||||
|
if (ResolveSrgbAttachmentWriteFormat(format, false) != format &&
|
||||||
|
(aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0 &&
|
||||||
|
m_mutableFormatUnsupported.find(format) == m_mutableFormatUnsupported.end()) {
|
||||||
|
imageCreateFlags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
VkImageUsageFlags desiredUsage =
|
VkImageUsageFlags desiredUsage =
|
||||||
VK_IMAGE_USAGE_SAMPLED_BIT |
|
VK_IMAGE_USAGE_SAMPLED_BIT |
|
||||||
@@ -2068,6 +2094,51 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pure-depth images whose canonical shadow layout differs from the image texel
|
||||||
|
// layout (the shadow keeps a full-scale 16/32-bit unorm word or a float; the
|
||||||
|
// image may be X8_D24 or a D32_SFLOAT fallback) convert per texel here.
|
||||||
|
if (uploadAspectMask == VK_IMAGE_ASPECT_DEPTH_BIT) {
|
||||||
|
const TextureInternalFormat depthInternal = mipmapTexture.GetFormat();
|
||||||
|
const Bool shadowIsFloat = depthInternal == TextureInternalFormat::DepthComponent32F;
|
||||||
|
const Bool dstIsFloat = outResource.format == VK_FORMAT_D32_SFLOAT;
|
||||||
|
const Bool dstIsD24Word = outResource.format == VK_FORMAT_X8_D24_UNORM_PACK32;
|
||||||
|
stagingSize = 0;
|
||||||
|
for (auto& item : uploadItems) {
|
||||||
|
const SizeT texelCount = static_cast<SizeT>(item.texelSize.x()) *
|
||||||
|
static_cast<SizeT>(item.texelSize.y()) *
|
||||||
|
static_cast<SizeT>(std::max(item.texelSize.z(), 1));
|
||||||
|
const SizeT shadowTexelSize = item.uploadByteSize / std::max<SizeT>(texelCount, 1);
|
||||||
|
const Bool needsConversion =
|
||||||
|
(dstIsFloat && !shadowIsFloat) || (dstIsD24Word && shadowTexelSize == 4 && !shadowIsFloat);
|
||||||
|
if (needsConversion) {
|
||||||
|
Vector<Uint8> converted(texelCount * 4);
|
||||||
|
const Uint8* shadow = static_cast<const Uint8*>(item.source);
|
||||||
|
for (SizeT t = 0; t < texelCount; ++t) {
|
||||||
|
Uint32 wide = 0;
|
||||||
|
if (shadowTexelSize == 2) {
|
||||||
|
Uint16 raw = 0;
|
||||||
|
std::memcpy(&raw, shadow + t * 2, sizeof(raw));
|
||||||
|
wide = (static_cast<Uint32>(raw) << 16) | raw;
|
||||||
|
} else {
|
||||||
|
std::memcpy(&wide, shadow + t * 4, sizeof(wide));
|
||||||
|
}
|
||||||
|
if (dstIsFloat) {
|
||||||
|
const float value = static_cast<float>(static_cast<double>(wide) / 4294967295.0);
|
||||||
|
std::memcpy(converted.data() + t * 4, &value, sizeof(value));
|
||||||
|
} else { // X8_D24: depth in the low 24 bits of a 32-bit word
|
||||||
|
const Uint32 word = wide >> 8;
|
||||||
|
std::memcpy(converted.data() + t * 4, &word, sizeof(word));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item.expandedData = Move(converted);
|
||||||
|
item.source = item.expandedData.data();
|
||||||
|
item.uploadByteSize = item.expandedData.size();
|
||||||
|
}
|
||||||
|
item.offset = stagingSize;
|
||||||
|
stagingSize += static_cast<VkDeviceSize>(item.uploadByteSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
VkBuffer stagingBuffer = VK_NULL_HANDLE;
|
||||||
VmaAllocation stagingAllocation = nullptr;
|
VmaAllocation stagingAllocation = nullptr;
|
||||||
|
|
||||||
|
|||||||
@@ -43,8 +43,11 @@ namespace MobileGL {
|
|||||||
case TextureInternalFormat::SRGB8:
|
case TextureInternalFormat::SRGB8:
|
||||||
case TextureInternalFormat::RGB8I:
|
case TextureInternalFormat::RGB8I:
|
||||||
case TextureInternalFormat::RGB8UI:
|
case TextureInternalFormat::RGB8UI:
|
||||||
case TextureInternalFormat::DepthComponent24:
|
|
||||||
return 3;
|
return 3;
|
||||||
|
// Canonical depth shadow is a full 32-bit unorm word (see PixelStoreProcessor),
|
||||||
|
// converted at upload to the image's own 24/32-bit layout.
|
||||||
|
case TextureInternalFormat::DepthComponent24:
|
||||||
|
return 4;
|
||||||
|
|
||||||
case TextureInternalFormat::RGBA2:
|
case TextureInternalFormat::RGBA2:
|
||||||
case TextureInternalFormat::RGBA4:
|
case TextureInternalFormat::RGBA4:
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
Int32,
|
Int32,
|
||||||
Half,
|
Half,
|
||||||
Float32,
|
Float32,
|
||||||
|
UNorm32, // 32-bit fixed-point depth shadow
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InternalShadowLayout {
|
struct InternalShadowLayout {
|
||||||
@@ -123,6 +124,21 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
|
|
||||||
Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) {
|
Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) {
|
||||||
switch (internal) {
|
switch (internal) {
|
||||||
|
// Depth shadows follow TextureFormatProcessor::NormalizePixelFormat: 16-bit
|
||||||
|
// unorm for DEPTH_COMPONENT16, 32-bit unorm for the 24/32-bit fixed-point
|
||||||
|
// depths, float for DEPTH_COMPONENT32F.
|
||||||
|
case TextureInternalFormat::DepthComponent16:
|
||||||
|
out = {1, ShadowComponent::UNorm16, false};
|
||||||
|
return true;
|
||||||
|
case TextureInternalFormat::DepthComponent24:
|
||||||
|
case TextureInternalFormat::DepthComponent32:
|
||||||
|
case TextureInternalFormat::DepthComponent:
|
||||||
|
out = {1, ShadowComponent::UNorm32, false};
|
||||||
|
return true;
|
||||||
|
case TextureInternalFormat::DepthComponent32F:
|
||||||
|
out = {1, ShadowComponent::Float32, false};
|
||||||
|
return true;
|
||||||
|
|
||||||
case TextureInternalFormat::R8:
|
case TextureInternalFormat::R8:
|
||||||
case TextureInternalFormat::Red: out = {1, ShadowComponent::UNorm8, false}; return true;
|
case TextureInternalFormat::Red: out = {1, ShadowComponent::UNorm8, false}; return true;
|
||||||
case TextureInternalFormat::RG8:
|
case TextureInternalFormat::RG8:
|
||||||
@@ -299,8 +315,10 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
case TextureInputFormat::RGBAInteger: out = {{0, 1, 2, 3}, 4, true}; return true;
|
case TextureInputFormat::RGBAInteger: out = {{0, 1, 2, 3}, 4, true}; return true;
|
||||||
case TextureInputFormat::BGRA: out = {{2, 1, 0, 3}, 4, false}; return true;
|
case TextureInputFormat::BGRA: out = {{2, 1, 0, 3}, 4, false}; return true;
|
||||||
case TextureInputFormat::BGRAInteger: out = {{2, 1, 0, 3}, 4, true}; return true;
|
case TextureInputFormat::BGRAInteger: out = {{2, 1, 0, 3}, 4, true}; return true;
|
||||||
|
// A depth value converts like a single normalized/float channel.
|
||||||
|
case TextureInputFormat::DepthComponent: out = {{0, -1, -1, -1}, 1, false}; return true;
|
||||||
default:
|
default:
|
||||||
return false; // depth / stencil / unknown
|
return false; // stencil / packed depth-stencil / unknown
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,8 +364,7 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
out = isInteger ? ShadowComponent::Int16 : ShadowComponent::SNorm16;
|
out = isInteger ? ShadowComponent::Int16 : ShadowComponent::SNorm16;
|
||||||
return true;
|
return true;
|
||||||
case TexturePixelDataType::UnsignedInt:
|
case TexturePixelDataType::UnsignedInt:
|
||||||
if (!isInteger) return false; // no 32-bit normalized shadow layout
|
out = isInteger ? ShadowComponent::UInt32 : ShadowComponent::UNorm32;
|
||||||
out = ShadowComponent::UInt32;
|
|
||||||
return true;
|
return true;
|
||||||
case TexturePixelDataType::Int:
|
case TexturePixelDataType::Int:
|
||||||
if (!isInteger) return false;
|
if (!isInteger) return false;
|
||||||
@@ -617,6 +634,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
case ShadowComponent::Float32:
|
case ShadowComponent::Float32:
|
||||||
Memcpy(dst, &v, sizeof(v));
|
Memcpy(dst, &v, sizeof(v));
|
||||||
break;
|
break;
|
||||||
|
case ShadowComponent::UNorm32: {
|
||||||
|
const auto out = static_cast<Uint32>(
|
||||||
|
std::llround(static_cast<double>(std::clamp(v, 0.0f, 1.0f)) * 4294967295.0));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break; // integer components never reach the float encoder
|
break; // integer components never reach the float encoder
|
||||||
}
|
}
|
||||||
@@ -771,6 +794,64 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
const Int effectiveHeight = (params.ImageHeight > 0) ? params.ImageHeight : height;
|
const Int effectiveHeight = (params.ImageHeight > 0) ? params.ImageHeight : height;
|
||||||
const SizeT inputRowStride = CalculateRowStride(effectiveWidth, pixelSize, params.Alignment);
|
const SizeT inputRowStride = CalculateRowStride(effectiveWidth, pixelSize, params.Alignment);
|
||||||
|
|
||||||
|
// GL_DEPTH_COMPONENT client data may populate a packed depth-stencil internal
|
||||||
|
// format (the stencil half becomes zero); the generic channel converter cannot
|
||||||
|
// express the packed shadow words, so convert here.
|
||||||
|
const Bool packedDepthStencilInternal = targetInternalFormat == TextureInternalFormat::Depth24Stencil8 ||
|
||||||
|
targetInternalFormat == TextureInternalFormat::DepthStencil ||
|
||||||
|
targetInternalFormat == TextureInternalFormat::Depth32FStencil8;
|
||||||
|
if (!isBitmap && packedDepthStencilInternal && textureInputFormat == TextureInputFormat::DepthComponent &&
|
||||||
|
(inputDataType == TexturePixelDataType::Float || inputDataType == TexturePixelDataType::UnsignedInt ||
|
||||||
|
inputDataType == TexturePixelDataType::UnsignedShort)) {
|
||||||
|
const Bool floatShadow = targetInternalFormat == TextureInternalFormat::Depth32FStencil8;
|
||||||
|
const SizeT outPixelSize = floatShadow ? 8 : 4;
|
||||||
|
outSize = static_cast<SizeT>(width) * height * std::max(depth, 1) * outPixelSize;
|
||||||
|
Uint8* outputPixels = static_cast<Uint8*>(malloc(outSize));
|
||||||
|
if (!outputPixels) {
|
||||||
|
outSize = 0;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const Uint8* srcBase = static_cast<const Uint8*>(inputPixels) +
|
||||||
|
static_cast<SizeT>(params.SkipImages) * static_cast<SizeT>(effectiveHeight) * inputRowStride +
|
||||||
|
static_cast<SizeT>(params.SkipRows) * inputRowStride +
|
||||||
|
static_cast<SizeT>(params.SkipPixels) * pixelSize;
|
||||||
|
Uint8* dst = outputPixels;
|
||||||
|
for (Int z = 0; z < std::max(depth, 1); ++z) {
|
||||||
|
for (Int y = 0; y < height; ++y) {
|
||||||
|
const Uint8* srcRow = srcBase +
|
||||||
|
static_cast<SizeT>(z) * static_cast<SizeT>(effectiveHeight) * inputRowStride +
|
||||||
|
static_cast<SizeT>(y) * inputRowStride;
|
||||||
|
for (Int x = 0; x < width; ++x) {
|
||||||
|
Float depthValue = 0.0f;
|
||||||
|
if (inputDataType == TexturePixelDataType::Float) {
|
||||||
|
Memcpy(&depthValue, srcRow + static_cast<SizeT>(x) * 4, sizeof(depthValue));
|
||||||
|
} else if (inputDataType == TexturePixelDataType::UnsignedInt) {
|
||||||
|
Uint32 raw = 0;
|
||||||
|
Memcpy(&raw, srcRow + static_cast<SizeT>(x) * 4, sizeof(raw));
|
||||||
|
depthValue = static_cast<Float>(static_cast<double>(raw) / 4294967295.0);
|
||||||
|
} else {
|
||||||
|
Uint16 raw = 0;
|
||||||
|
Memcpy(&raw, srcRow + static_cast<SizeT>(x) * 2, sizeof(raw));
|
||||||
|
depthValue = static_cast<Float>(raw) / 65535.0f;
|
||||||
|
}
|
||||||
|
if (floatShadow) {
|
||||||
|
const Uint32 stencilWord = 0;
|
||||||
|
Memcpy(dst, &depthValue, sizeof(depthValue));
|
||||||
|
Memcpy(dst + 4, &stencilWord, sizeof(stencilWord));
|
||||||
|
dst += 8;
|
||||||
|
} else {
|
||||||
|
const Uint32 depth24 = static_cast<Uint32>(
|
||||||
|
std::llround(static_cast<double>(std::clamp(depthValue, 0.0f, 1.0f)) * 16777215.0));
|
||||||
|
const Uint32 word = depth24 << 8;
|
||||||
|
Memcpy(dst, &word, sizeof(word));
|
||||||
|
dst += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return outputPixels;
|
||||||
|
}
|
||||||
|
|
||||||
UnpackConversionSpec conversion{};
|
UnpackConversionSpec conversion{};
|
||||||
const Bool needConversion =
|
const Bool needConversion =
|
||||||
!isBitmap && GetUnpackConversionSpec(targetInternalFormat, textureInputFormat, inputDataType, conversion);
|
!isBitmap && GetUnpackConversionSpec(targetInternalFormat, textureInputFormat, inputDataType, conversion);
|
||||||
@@ -972,6 +1053,11 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
Memcpy(&v, p, sizeof(v));
|
Memcpy(&v, p, sizeof(v));
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
case ShadowComponent::UNorm32: {
|
||||||
|
Uint32 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return static_cast<Float>(static_cast<double>(v) / 4294967295.0);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user