mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix] (MG_Util/Texture): expand channels and convert component types on texture unpack to the internal shadow layout
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/FramebufferEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
|
#include <MG_Util/Converters/MGToStr/TextureEnumConverter.h>
|
||||||
#include <MG_Util/Converters/MGToGL/RenderStateEnumConverter.h>
|
#include <MG_Util/Converters/MGToGL/RenderStateEnumConverter.h>
|
||||||
|
#include <MG_Util/Math/HalfFloat.h>
|
||||||
#include <MG_Util/Metrics/BufferMetrics.h>
|
#include <MG_Util/Metrics/BufferMetrics.h>
|
||||||
#include <MG_Util/Texture/PixelStoreProcessor.h>
|
#include <MG_Util/Texture/PixelStoreProcessor.h>
|
||||||
#include <Config.h>
|
#include <Config.h>
|
||||||
@@ -3270,62 +3271,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
|||||||
// wide RGBA format into scratch memory and repack into the caller's (format, type) layout on the CPU,
|
// wide RGBA format into scratch memory and repack into the caller's (format, type) layout on the CPU,
|
||||||
// honoring the client-side PACK pixel-store parameters.
|
// honoring the client-side PACK pixel-store parameters.
|
||||||
|
|
||||||
static Float DecodeHalfBitsToFloat(Uint16 half) {
|
using MG_Util::DecodeHalfBitsToFloat;
|
||||||
const Uint32 sign = static_cast<Uint32>(half & 0x8000u) << 16;
|
using MG_Util::EncodeFloatToHalfBits;
|
||||||
const Uint32 exponent = (half >> 10) & 0x1Fu;
|
|
||||||
const Uint32 mantissa = half & 0x3FFu;
|
|
||||||
Uint32 bits;
|
|
||||||
if (exponent == 0) {
|
|
||||||
if (mantissa == 0) {
|
|
||||||
bits = sign; // signed zero
|
|
||||||
} else {
|
|
||||||
// Subnormal half: renormalize into a float exponent.
|
|
||||||
Uint32 e = 127 - 15 + 1;
|
|
||||||
Uint32 m = mantissa;
|
|
||||||
while ((m & 0x400u) == 0) {
|
|
||||||
m <<= 1;
|
|
||||||
--e;
|
|
||||||
}
|
|
||||||
bits = sign | (e << 23) | ((m & 0x3FFu) << 13);
|
|
||||||
}
|
|
||||||
} else if (exponent == 31) {
|
|
||||||
bits = sign | 0x7F800000u | (mantissa << 13); // Inf / NaN
|
|
||||||
} else {
|
|
||||||
bits = sign | ((exponent + 112) << 23) | (mantissa << 13);
|
|
||||||
}
|
|
||||||
return std::bit_cast<Float>(bits);
|
|
||||||
}
|
|
||||||
|
|
||||||
static Uint16 EncodeFloatToHalfBits(Float value) {
|
|
||||||
const Uint32 bits = std::bit_cast<Uint32>(value);
|
|
||||||
const auto sign = static_cast<Uint16>((bits >> 16) & 0x8000u);
|
|
||||||
const Uint32 exponent = (bits >> 23) & 0xFFu;
|
|
||||||
const Uint32 mantissa = bits & 0x7FFFFFu;
|
|
||||||
if (exponent == 0xFF) { // Inf / NaN
|
|
||||||
return static_cast<Uint16>(sign | 0x7C00u | (mantissa != 0 ? 0x200u : 0u));
|
|
||||||
}
|
|
||||||
const Int32 halfExponent = static_cast<Int32>(exponent) - 127 + 15;
|
|
||||||
if (halfExponent >= 31) {
|
|
||||||
return static_cast<Uint16>(sign | 0x7C00u); // overflow -> Inf
|
|
||||||
}
|
|
||||||
if (halfExponent <= 0) {
|
|
||||||
if (halfExponent < -10) {
|
|
||||||
return sign; // underflow -> signed zero
|
|
||||||
}
|
|
||||||
const Uint32 m = mantissa | 0x800000u;
|
|
||||||
const Uint32 shift = static_cast<Uint32>(14 - halfExponent);
|
|
||||||
Uint32 half = m >> shift;
|
|
||||||
if ((m >> (shift - 1)) & 1u) {
|
|
||||||
++half; // round to nearest
|
|
||||||
}
|
|
||||||
return static_cast<Uint16>(sign | half);
|
|
||||||
}
|
|
||||||
Uint32 half = (static_cast<Uint32>(halfExponent) << 10) | (mantissa >> 13);
|
|
||||||
if (mantissa & 0x1000u) {
|
|
||||||
++half; // round to nearest; a carry into the exponent is the correct result
|
|
||||||
}
|
|
||||||
return static_cast<Uint16>(sign | half);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ReadbackChannelMapping {
|
struct ReadbackChannelMapping {
|
||||||
Int sourceChannel[4]; // RGBA source channel feeding each destination channel
|
Int sourceChannel[4]; // RGBA source channel feeding each destination channel
|
||||||
|
|||||||
@@ -750,6 +750,163 @@ TEST_F(TextureTest, GetInternalformativReportsBasicTextureMetadata) {
|
|||||||
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DExpandsRedUnsignedByteToRgba8) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint8 pixels[] = {
|
||||||
|
10, 20,
|
||||||
|
30, 40,
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RED, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
const Uint8 expected[] = {
|
||||||
|
10, 0, 0, 255,
|
||||||
|
20, 0, 0, 255,
|
||||||
|
30, 0, 0, 255,
|
||||||
|
40, 0, 0, 255,
|
||||||
|
};
|
||||||
|
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||||
|
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexSubImage2DExpandsRgUnsignedByteToRgba8) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||||
|
|
||||||
|
const Uint8 pixels[] = {
|
||||||
|
10, 20,
|
||||||
|
30, 40,
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::TexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 1, GL_RG, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
const Uint8 expected[] = {
|
||||||
|
10, 20, 0, 255,
|
||||||
|
30, 40, 0, 255,
|
||||||
|
};
|
||||||
|
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||||
|
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DReordersBgrUnsignedByteToRgba8) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint8 pixels[] = {
|
||||||
|
1, 2, 3,
|
||||||
|
4, 5, 6,
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 1, 0, GL_BGR, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
const Uint8 expected[] = {
|
||||||
|
3, 2, 1, 255,
|
||||||
|
6, 5, 4, 255,
|
||||||
|
};
|
||||||
|
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||||
|
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DConvertsRedFloatToRgba8) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const GLfloat pixels[] = {
|
||||||
|
0.0f, 0.5f,
|
||||||
|
1.0f, 2.0f, // out-of-range values clamp to [0, 1]
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RED, GL_FLOAT, pixels);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
const Uint8 expected[] = {
|
||||||
|
0, 0, 0, 255,
|
||||||
|
128, 0, 0, 255,
|
||||||
|
255, 0, 0, 255,
|
||||||
|
255, 0, 0, 255,
|
||||||
|
};
|
||||||
|
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||||
|
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DExpandsRedIntegerUnsignedShortToRgba8ui) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint16 pixels[] = {
|
||||||
|
10, 300, // 300 exceeds the 8-bit destination and clamps to 255
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8UI, 2, 1, 0, GL_RED_INTEGER, GL_UNSIGNED_SHORT, pixels);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
const Uint8 expected[] = {
|
||||||
|
10, 0, 0, 1, // integer formats default missing alpha to 1, not the type maximum
|
||||||
|
255, 0, 0, 1,
|
||||||
|
};
|
||||||
|
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||||
|
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(TextureTest, BoundTexImage2DExpandsRedToRgba8WithRowLengthAndSkips) {
|
||||||
|
GLuint texture = 0;
|
||||||
|
MG_Impl::GLImpl::GenTextures(1, &texture);
|
||||||
|
MG_Impl::GLImpl::BindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
|
||||||
|
const Uint8 pixels[] = {
|
||||||
|
1, 2, 3, 4,
|
||||||
|
5, 6, 7, 8,
|
||||||
|
9, 10, 11, 12,
|
||||||
|
};
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 4);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_PIXELS, 1);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_ROWS, 1);
|
||||||
|
MG_Impl::GLImpl::TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_RED, GL_UNSIGNED_BYTE, pixels);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_SKIP_ROWS, 0);
|
||||||
|
MG_Impl::GLImpl::PixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
|
|
||||||
|
const auto* stored = GetBoundTexture2DLevelBytes(texture);
|
||||||
|
const Uint8 expected[] = {
|
||||||
|
6, 0, 0, 255,
|
||||||
|
7, 0, 0, 255,
|
||||||
|
10, 0, 0, 255,
|
||||||
|
11, 0, 0, 255,
|
||||||
|
};
|
||||||
|
for (SizeT i = 0; i < sizeof(expected); ++i) {
|
||||||
|
EXPECT_EQ(stored[i], expected[i]) << "byte " << i;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(TextureTest, NormalizeDepth24Stencil8UsesPackedDepthStencilType) {
|
TEST_F(TextureTest, NormalizeDepth24Stencil8UsesPackedDepthStencilType) {
|
||||||
GLenum internalFormat = 0;
|
GLenum internalFormat = 0;
|
||||||
GLenum format = 0;
|
GLenum format = 0;
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
// MobileGL - MobileGL/MG_Util/Math/HalfFloat.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 <Includes.h>
|
||||||
|
|
||||||
|
namespace MobileGL::MG_Util {
|
||||||
|
inline Float DecodeHalfBitsToFloat(Uint16 half) {
|
||||||
|
const Uint32 sign = static_cast<Uint32>(half & 0x8000u) << 16;
|
||||||
|
const Uint32 exponent = (half >> 10) & 0x1Fu;
|
||||||
|
const Uint32 mantissa = half & 0x3FFu;
|
||||||
|
Uint32 bits;
|
||||||
|
if (exponent == 0) {
|
||||||
|
if (mantissa == 0) {
|
||||||
|
bits = sign; // signed zero
|
||||||
|
} else {
|
||||||
|
// Subnormal half: renormalize into a float exponent.
|
||||||
|
Uint32 e = 127 - 15 + 1;
|
||||||
|
Uint32 m = mantissa;
|
||||||
|
while ((m & 0x400u) == 0) {
|
||||||
|
m <<= 1;
|
||||||
|
--e;
|
||||||
|
}
|
||||||
|
bits = sign | (e << 23) | ((m & 0x3FFu) << 13);
|
||||||
|
}
|
||||||
|
} else if (exponent == 31) {
|
||||||
|
bits = sign | 0x7F800000u | (mantissa << 13); // Inf / NaN
|
||||||
|
} else {
|
||||||
|
bits = sign | ((exponent + 112) << 23) | (mantissa << 13);
|
||||||
|
}
|
||||||
|
return std::bit_cast<Float>(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Uint16 EncodeFloatToHalfBits(Float value) {
|
||||||
|
const Uint32 bits = std::bit_cast<Uint32>(value);
|
||||||
|
const auto sign = static_cast<Uint16>((bits >> 16) & 0x8000u);
|
||||||
|
const Uint32 exponent = (bits >> 23) & 0xFFu;
|
||||||
|
const Uint32 mantissa = bits & 0x7FFFFFu;
|
||||||
|
if (exponent == 0xFF) { // Inf / NaN
|
||||||
|
return static_cast<Uint16>(sign | 0x7C00u | (mantissa != 0 ? 0x200u : 0u));
|
||||||
|
}
|
||||||
|
const Int32 halfExponent = static_cast<Int32>(exponent) - 127 + 15;
|
||||||
|
if (halfExponent >= 31) {
|
||||||
|
return static_cast<Uint16>(sign | 0x7C00u); // overflow -> Inf
|
||||||
|
}
|
||||||
|
if (halfExponent <= 0) {
|
||||||
|
if (halfExponent < -10) {
|
||||||
|
return sign; // underflow -> signed zero
|
||||||
|
}
|
||||||
|
const Uint32 m = mantissa | 0x800000u;
|
||||||
|
const Uint32 shift = static_cast<Uint32>(14 - halfExponent);
|
||||||
|
Uint32 half = m >> shift;
|
||||||
|
if ((m >> (shift - 1)) & 1u) {
|
||||||
|
++half; // round to nearest
|
||||||
|
}
|
||||||
|
return static_cast<Uint16>(sign | half);
|
||||||
|
}
|
||||||
|
Uint32 half = (static_cast<Uint32>(halfExponent) << 10) | (mantissa >> 13);
|
||||||
|
if (mantissa & 0x1000u) {
|
||||||
|
++half; // round to nearest; a carry into the exponent is the correct result
|
||||||
|
}
|
||||||
|
return static_cast<Uint16>(sign | half);
|
||||||
|
}
|
||||||
|
} // namespace MobileGL::MG_Util
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
// End of Source File Header
|
// End of Source File Header
|
||||||
|
|
||||||
#include "PixelStoreProcessor.h"
|
#include "PixelStoreProcessor.h"
|
||||||
|
#include "MG_Util/Math/HalfFloat.h"
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace MobileGL::MG_Util::PixelStoreProcessor {
|
namespace MobileGL::MG_Util::PixelStoreProcessor {
|
||||||
static SizeT CalculateRowStride(Int width, SizeT pixelSize, Int alignment) {
|
static SizeT CalculateRowStride(Int width, SizeT pixelSize, Int alignment) {
|
||||||
@@ -70,30 +72,523 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Bool GetRgba8ByteSwizzleForUnpack(TextureInputFormat inputFormat, TexturePixelDataType inputDataType,
|
// ---- Unpack channel expansion / type conversion ------------------------------------------------------------
|
||||||
Vector<TextureSwizzleParam>& swizzle) {
|
// The shadow mip buffer stores every level in the internal format's canonical layout: its channels in
|
||||||
if (inputFormat == TextureInputFormat::RGBA) {
|
// R,G,B(,A) order, encoded with the component type the backends upload with (see
|
||||||
if (inputDataType == TexturePixelDataType::UnsignedInt8888) {
|
// TextureFormatProcessor::NormalizePixelFormat; channelCount * componentSize matches
|
||||||
swizzle = {TextureSwizzleParam::Alpha, TextureSwizzleParam::Blue, TextureSwizzleParam::Green,
|
// GetSizedInternalFormatSizeInBytes for every format listed below). When the client's (format, type)
|
||||||
TextureSwizzleParam::Red};
|
// does not already produce that byte layout, each texel is decoded to RGBA (float for normalized/float
|
||||||
return true;
|
// formats, integer for *_INTEGER formats, missing G/B = 0 and missing A = 1) and re-encoded.
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
enum class ShadowComponent {
|
||||||
|
UNorm8,
|
||||||
|
SNorm8,
|
||||||
|
UNorm16,
|
||||||
|
SNorm16,
|
||||||
|
UInt8,
|
||||||
|
Int8,
|
||||||
|
UInt16,
|
||||||
|
Int16,
|
||||||
|
UInt32,
|
||||||
|
Int32,
|
||||||
|
Half,
|
||||||
|
Float32,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct InternalShadowLayout {
|
||||||
|
Int channelCount;
|
||||||
|
ShadowComponent component;
|
||||||
|
Bool isInteger;
|
||||||
|
};
|
||||||
|
|
||||||
|
SizeT GetShadowComponentSize(ShadowComponent component) {
|
||||||
|
switch (component) {
|
||||||
|
case ShadowComponent::UNorm8:
|
||||||
|
case ShadowComponent::SNorm8:
|
||||||
|
case ShadowComponent::UInt8:
|
||||||
|
case ShadowComponent::Int8:
|
||||||
|
return 1;
|
||||||
|
case ShadowComponent::UNorm16:
|
||||||
|
case ShadowComponent::SNorm16:
|
||||||
|
case ShadowComponent::UInt16:
|
||||||
|
case ShadowComponent::Int16:
|
||||||
|
case ShadowComponent::Half:
|
||||||
|
return 2;
|
||||||
|
default:
|
||||||
|
return 4;
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputFormat == TextureInputFormat::BGRA) {
|
Bool GetInternalShadowLayout(TextureInternalFormat internal, InternalShadowLayout& out) {
|
||||||
if (inputDataType == TexturePixelDataType::UnsignedInt8888) {
|
switch (internal) {
|
||||||
swizzle = {TextureSwizzleParam::Green, TextureSwizzleParam::Blue, TextureSwizzleParam::Alpha,
|
case TextureInternalFormat::R8: out = {1, ShadowComponent::UNorm8, false}; return true;
|
||||||
TextureSwizzleParam::Red};
|
case TextureInternalFormat::RG8: out = {2, ShadowComponent::UNorm8, false}; return true;
|
||||||
} else {
|
case TextureInternalFormat::RGB8:
|
||||||
swizzle = {TextureSwizzleParam::Blue, TextureSwizzleParam::Green, TextureSwizzleParam::Red,
|
case TextureInternalFormat::SRGB8: out = {3, ShadowComponent::UNorm8, false}; return true;
|
||||||
TextureSwizzleParam::Alpha};
|
case TextureInternalFormat::RGBA8:
|
||||||
|
case TextureInternalFormat::SRGB8Alpha8: out = {4, ShadowComponent::UNorm8, false}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R8Snorm: out = {1, ShadowComponent::SNorm8, false}; return true;
|
||||||
|
case TextureInternalFormat::RG8Snorm: out = {2, ShadowComponent::SNorm8, false}; return true;
|
||||||
|
case TextureInternalFormat::RGB8Snorm: out = {3, ShadowComponent::SNorm8, false}; return true;
|
||||||
|
case TextureInternalFormat::RGBA8Snorm: out = {4, ShadowComponent::SNorm8, false}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R16: out = {1, ShadowComponent::UNorm16, false}; return true;
|
||||||
|
case TextureInternalFormat::RG16: out = {2, ShadowComponent::UNorm16, false}; return true;
|
||||||
|
case TextureInternalFormat::RGB16: out = {3, ShadowComponent::UNorm16, false}; return true;
|
||||||
|
case TextureInternalFormat::RGBA16: out = {4, ShadowComponent::UNorm16, false}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R16Snorm: out = {1, ShadowComponent::SNorm16, false}; return true;
|
||||||
|
case TextureInternalFormat::RG16Snorm: out = {2, ShadowComponent::SNorm16, false}; return true;
|
||||||
|
case TextureInternalFormat::RGB16Snorm: out = {3, ShadowComponent::SNorm16, false}; return true;
|
||||||
|
case TextureInternalFormat::RGBA16Snorm: out = {4, ShadowComponent::SNorm16, false}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R16F: out = {1, ShadowComponent::Half, false}; return true;
|
||||||
|
case TextureInternalFormat::RG16F: out = {2, ShadowComponent::Half, false}; return true;
|
||||||
|
case TextureInternalFormat::RGB16F: out = {3, ShadowComponent::Half, false}; return true;
|
||||||
|
case TextureInternalFormat::RGBA16F: out = {4, ShadowComponent::Half, false}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R32F: out = {1, ShadowComponent::Float32, false}; return true;
|
||||||
|
case TextureInternalFormat::RG32F: out = {2, ShadowComponent::Float32, false}; return true;
|
||||||
|
case TextureInternalFormat::RGB32F: out = {3, ShadowComponent::Float32, false}; return true;
|
||||||
|
case TextureInternalFormat::RGBA32F: out = {4, ShadowComponent::Float32, false}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R8UI: out = {1, ShadowComponent::UInt8, true}; return true;
|
||||||
|
case TextureInternalFormat::RG8UI: out = {2, ShadowComponent::UInt8, true}; return true;
|
||||||
|
case TextureInternalFormat::RGB8UI: out = {3, ShadowComponent::UInt8, true}; return true;
|
||||||
|
case TextureInternalFormat::RGBA8UI: out = {4, ShadowComponent::UInt8, true}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R8I: out = {1, ShadowComponent::Int8, true}; return true;
|
||||||
|
case TextureInternalFormat::RG8I: out = {2, ShadowComponent::Int8, true}; return true;
|
||||||
|
case TextureInternalFormat::RGB8I: out = {3, ShadowComponent::Int8, true}; return true;
|
||||||
|
case TextureInternalFormat::RGBA8I: out = {4, ShadowComponent::Int8, true}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R16UI: out = {1, ShadowComponent::UInt16, true}; return true;
|
||||||
|
case TextureInternalFormat::RG16UI: out = {2, ShadowComponent::UInt16, true}; return true;
|
||||||
|
case TextureInternalFormat::RGB16UI: out = {3, ShadowComponent::UInt16, true}; return true;
|
||||||
|
case TextureInternalFormat::RGBA16UI: out = {4, ShadowComponent::UInt16, true}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R16I: out = {1, ShadowComponent::Int16, true}; return true;
|
||||||
|
case TextureInternalFormat::RG16I: out = {2, ShadowComponent::Int16, true}; return true;
|
||||||
|
case TextureInternalFormat::RGB16I: out = {3, ShadowComponent::Int16, true}; return true;
|
||||||
|
case TextureInternalFormat::RGBA16I: out = {4, ShadowComponent::Int16, true}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R32UI: out = {1, ShadowComponent::UInt32, true}; return true;
|
||||||
|
case TextureInternalFormat::RG32UI: out = {2, ShadowComponent::UInt32, true}; return true;
|
||||||
|
case TextureInternalFormat::RGB32UI: out = {3, ShadowComponent::UInt32, true}; return true;
|
||||||
|
case TextureInternalFormat::RGBA32UI: out = {4, ShadowComponent::UInt32, true}; return true;
|
||||||
|
|
||||||
|
case TextureInternalFormat::R32I: out = {1, ShadowComponent::Int32, true}; return true;
|
||||||
|
case TextureInternalFormat::RG32I: out = {2, ShadowComponent::Int32, true}; return true;
|
||||||
|
case TextureInternalFormat::RGB32I: out = {3, ShadowComponent::Int32, true}; return true;
|
||||||
|
case TextureInternalFormat::RGBA32I: out = {4, ShadowComponent::Int32, true}; return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Packed internal layouts (RGB5A1, RGB10A2, RGB9E5, ...), depth/stencil and unsized formats
|
||||||
|
// keep the legacy copy path.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UnpackChannelMapping {
|
||||||
|
Int formatPosition[4]; // position of R,G,B,A within the input format's component list; -1 = missing
|
||||||
|
Int channelCount;
|
||||||
|
Bool isInteger;
|
||||||
|
};
|
||||||
|
|
||||||
|
Bool GetUnpackChannelMapping(TextureInputFormat format, UnpackChannelMapping& out) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureInputFormat::Red: out = {{0, -1, -1, -1}, 1, false}; return true;
|
||||||
|
case TextureInputFormat::RInteger: out = {{0, -1, -1, -1}, 1, true}; return true;
|
||||||
|
case TextureInputFormat::RG: out = {{0, 1, -1, -1}, 2, false}; return true;
|
||||||
|
case TextureInputFormat::RGInteger: out = {{0, 1, -1, -1}, 2, true}; return true;
|
||||||
|
case TextureInputFormat::RGB: out = {{0, 1, 2, -1}, 3, false}; return true;
|
||||||
|
case TextureInputFormat::RGBInteger: out = {{0, 1, 2, -1}, 3, true}; return true;
|
||||||
|
case TextureInputFormat::BGR: out = {{2, 1, 0, -1}, 3, false}; return true;
|
||||||
|
case TextureInputFormat::BGRInteger: out = {{2, 1, 0, -1}, 3, true}; return true;
|
||||||
|
case TextureInputFormat::RGBA: out = {{0, 1, 2, 3}, 4, false}; 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::BGRAInteger: out = {{2, 1, 0, 3}, 4, true}; return true;
|
||||||
|
default:
|
||||||
|
return false; // depth / stencil / unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PackedTypeLayout {
|
||||||
|
Int fieldCount;
|
||||||
|
Int width[4]; // bit width of each format component, in component order
|
||||||
|
Int totalBits;
|
||||||
|
Bool reversed; // *_REV: the first format component sits in the least significant bits
|
||||||
|
};
|
||||||
|
|
||||||
|
Bool GetPackedTypeLayout(TexturePixelDataType type, PackedTypeLayout& out) {
|
||||||
|
switch (type) {
|
||||||
|
case TexturePixelDataType::UnsignedByte332: out = {3, {3, 3, 2, 0}, 8, false}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedByte233Rev: out = {3, {3, 3, 2, 0}, 8, true}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort565: out = {3, {5, 6, 5, 0}, 16, false}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort565Rev: out = {3, {5, 6, 5, 0}, 16, true}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort4444: out = {4, {4, 4, 4, 4}, 16, false}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort4444Rev: out = {4, {4, 4, 4, 4}, 16, true}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort5551: out = {4, {5, 5, 5, 1}, 16, false}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort1555Rev: out = {4, {5, 5, 5, 1}, 16, true}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedInt8888: out = {4, {8, 8, 8, 8}, 32, false}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedInt8888Rev: out = {4, {8, 8, 8, 8}, 32, true}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedInt1010102: out = {4, {10, 10, 10, 2}, 32, false}; return true;
|
||||||
|
case TexturePixelDataType::UnsignedInt2101010Rev: out = {4, {10, 10, 10, 2}, 32, true}; return true;
|
||||||
|
default:
|
||||||
|
return false; // shared-exponent / packed-float / depth-stencil types stay on the legacy path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base data types whose in-memory encoding equals a shadow component encoding (fast-path check).
|
||||||
|
Bool GetDirectShadowComponentForType(TexturePixelDataType type, Bool isInteger, ShadowComponent& out) {
|
||||||
|
switch (type) {
|
||||||
|
case TexturePixelDataType::UnsignedByte:
|
||||||
|
out = isInteger ? ShadowComponent::UInt8 : ShadowComponent::UNorm8;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::Byte:
|
||||||
|
out = isInteger ? ShadowComponent::Int8 : ShadowComponent::SNorm8;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::UnsignedShort:
|
||||||
|
out = isInteger ? ShadowComponent::UInt16 : ShadowComponent::UNorm16;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::Short:
|
||||||
|
out = isInteger ? ShadowComponent::Int16 : ShadowComponent::SNorm16;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::UnsignedInt:
|
||||||
|
if (!isInteger) return false; // no 32-bit normalized shadow layout
|
||||||
|
out = ShadowComponent::UInt32;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::Int:
|
||||||
|
if (!isInteger) return false;
|
||||||
|
out = ShadowComponent::Int32;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::HalfFloat:
|
||||||
|
if (isInteger) return false;
|
||||||
|
out = ShadowComponent::Half;
|
||||||
|
return true;
|
||||||
|
case TexturePixelDataType::Float:
|
||||||
|
if (isInteger) return false;
|
||||||
|
out = ShadowComponent::Float32;
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Bool IsIdentityChannelOrder(const UnpackChannelMapping& mapping) {
|
||||||
|
for (Int i = 0; i < 4; ++i) {
|
||||||
|
const Int expected = i < mapping.channelCount ? i : -1;
|
||||||
|
if (mapping.formatPosition[i] != expected) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
struct UnpackConversionSpec {
|
||||||
}
|
UnpackChannelMapping mapping;
|
||||||
|
InternalShadowLayout internal;
|
||||||
|
PackedTypeLayout packed;
|
||||||
|
Bool isPacked;
|
||||||
|
TexturePixelDataType type;
|
||||||
|
SizeT inputPixelSize;
|
||||||
|
SizeT swapGroupSize; // UNPACK_SWAP_BYTES group: packed word size, or the component size
|
||||||
|
SizeT internalPixelSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns true when the (format, type) -> internal-format upload needs a per-texel conversion;
|
||||||
|
// returns false both for layouts that already match the shadow bytes (memcpy fast path) and for
|
||||||
|
// combinations the converter does not support (legacy copy behavior).
|
||||||
|
Bool GetUnpackConversionSpec(TextureInternalFormat internal, TextureInputFormat format,
|
||||||
|
TexturePixelDataType type, UnpackConversionSpec& out) {
|
||||||
|
InternalShadowLayout layout{};
|
||||||
|
if (!GetInternalShadowLayout(internal, layout)) return false;
|
||||||
|
UnpackChannelMapping mapping{};
|
||||||
|
if (!GetUnpackChannelMapping(format, mapping)) return false;
|
||||||
|
if (mapping.isInteger != layout.isInteger) return false; // rejected upstream; stay safe
|
||||||
|
|
||||||
|
PackedTypeLayout packed{};
|
||||||
|
const Bool isPacked = GetPackedTypeLayout(type, packed);
|
||||||
|
if (isPacked) {
|
||||||
|
if (packed.fieldCount != mapping.channelCount) return false;
|
||||||
|
// Byte layout already equals the RGBA8 shadow layout on little-endian.
|
||||||
|
if (internal == TextureInternalFormat::RGBA8 && format == TextureInputFormat::RGBA &&
|
||||||
|
type == TexturePixelDataType::UnsignedInt8888Rev) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ShadowComponent direct{};
|
||||||
|
const Bool hasDirect = GetDirectShadowComponentForType(type, mapping.isInteger, direct);
|
||||||
|
switch (type) {
|
||||||
|
case TexturePixelDataType::UnsignedByte:
|
||||||
|
case TexturePixelDataType::Byte:
|
||||||
|
case TexturePixelDataType::UnsignedShort:
|
||||||
|
case TexturePixelDataType::Short:
|
||||||
|
case TexturePixelDataType::UnsignedInt:
|
||||||
|
case TexturePixelDataType::Int:
|
||||||
|
break;
|
||||||
|
case TexturePixelDataType::Float:
|
||||||
|
case TexturePixelDataType::HalfFloat:
|
||||||
|
if (mapping.isInteger) return false; // rejected upstream
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasDirect && direct == layout.component && mapping.channelCount == layout.channelCount &&
|
||||||
|
IsIdentityChannelOrder(mapping)) {
|
||||||
|
return false; // input already matches the shadow layout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.mapping = mapping;
|
||||||
|
out.internal = layout;
|
||||||
|
out.packed = packed;
|
||||||
|
out.isPacked = isPacked;
|
||||||
|
out.type = type;
|
||||||
|
out.inputPixelSize = GetInputBytesPerPixel(format, type);
|
||||||
|
out.swapGroupSize = isPacked ? static_cast<SizeT>(packed.totalBits / 8)
|
||||||
|
: GetBaseTexturePixelDataTypeSize(type);
|
||||||
|
out.internalPixelSize =
|
||||||
|
static_cast<SizeT>(layout.channelCount) * GetShadowComponentSize(layout.component);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Float DecodeComponentToFloat(const Uint8* p, TexturePixelDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case TexturePixelDataType::UnsignedByte:
|
||||||
|
return static_cast<Float>(*p) / 255.0f;
|
||||||
|
case TexturePixelDataType::Byte: {
|
||||||
|
Int8 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return std::max(static_cast<Float>(v) / 127.0f, -1.0f);
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::UnsignedShort: {
|
||||||
|
Uint16 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return static_cast<Float>(v) / 65535.0f;
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::Short: {
|
||||||
|
Int16 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return std::max(static_cast<Float>(v) / 32767.0f, -1.0f);
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::UnsignedInt: {
|
||||||
|
Uint32 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return static_cast<Float>(static_cast<Double>(v) / 4294967295.0);
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::Int: {
|
||||||
|
Int32 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return static_cast<Float>(std::max(static_cast<Double>(v) / 2147483647.0, -1.0));
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::HalfFloat: {
|
||||||
|
Uint16 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return DecodeHalfBitsToFloat(v);
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::Float: {
|
||||||
|
Float v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Int64 DecodeComponentToInt(const Uint8* p, TexturePixelDataType type) {
|
||||||
|
switch (type) {
|
||||||
|
case TexturePixelDataType::UnsignedByte:
|
||||||
|
return *p;
|
||||||
|
case TexturePixelDataType::Byte: {
|
||||||
|
Int8 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::UnsignedShort: {
|
||||||
|
Uint16 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::Short: {
|
||||||
|
Int16 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::UnsignedInt: {
|
||||||
|
Uint32 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
case TexturePixelDataType::Int: {
|
||||||
|
Int32 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 ReadPackedWord(const Uint8* p, Int totalBits) {
|
||||||
|
switch (totalBits) {
|
||||||
|
case 8:
|
||||||
|
return *p;
|
||||||
|
case 16: {
|
||||||
|
Uint16 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
Uint32 v;
|
||||||
|
Memcpy(&v, p, sizeof(v));
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint32 ExtractPackedField(Uint32 word, const PackedTypeLayout& packed, Int position, Int& outWidth) {
|
||||||
|
Int shift;
|
||||||
|
if (packed.reversed) {
|
||||||
|
shift = 0;
|
||||||
|
for (Int i = 0; i < position; ++i) shift += packed.width[i];
|
||||||
|
} else {
|
||||||
|
shift = packed.totalBits;
|
||||||
|
for (Int i = 0; i <= position; ++i) shift -= packed.width[i];
|
||||||
|
}
|
||||||
|
outWidth = packed.width[position];
|
||||||
|
const Uint32 mask = (1u << outWidth) - 1u;
|
||||||
|
return (word >> shift) & mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncodeShadowComponentFloat(Uint8* dst, ShadowComponent component, Float v) {
|
||||||
|
switch (component) {
|
||||||
|
case ShadowComponent::UNorm8: {
|
||||||
|
const auto out = static_cast<Uint8>(std::llround(std::clamp(v, 0.0f, 1.0f) * 255.0));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::SNorm8: {
|
||||||
|
const auto out = static_cast<Int8>(std::llround(std::clamp(v, -1.0f, 1.0f) * 127.0));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::UNorm16: {
|
||||||
|
const auto out = static_cast<Uint16>(std::llround(std::clamp(v, 0.0f, 1.0f) * 65535.0));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::SNorm16: {
|
||||||
|
const auto out = static_cast<Int16>(std::llround(std::clamp(v, -1.0f, 1.0f) * 32767.0));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::Half: {
|
||||||
|
const Uint16 out = EncodeFloatToHalfBits(v);
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::Float32:
|
||||||
|
Memcpy(dst, &v, sizeof(v));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break; // integer components never reach the float encoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EncodeShadowComponentInt(Uint8* dst, ShadowComponent component, Int64 v) {
|
||||||
|
switch (component) {
|
||||||
|
case ShadowComponent::UInt8: {
|
||||||
|
const auto out = static_cast<Uint8>(std::clamp<Int64>(v, 0, 255));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::Int8: {
|
||||||
|
const auto out = static_cast<Int8>(std::clamp<Int64>(v, -128, 127));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::UInt16: {
|
||||||
|
const auto out = static_cast<Uint16>(std::clamp<Int64>(v, 0, 65535));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::Int16: {
|
||||||
|
const auto out = static_cast<Int16>(std::clamp<Int64>(v, -32768, 32767));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::UInt32: {
|
||||||
|
const auto out = static_cast<Uint32>(std::clamp<Int64>(v, 0, 4294967295LL));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShadowComponent::Int32: {
|
||||||
|
const auto out = static_cast<Int32>(std::clamp<Int64>(v, -2147483648LL, 2147483647LL));
|
||||||
|
Memcpy(dst, &out, sizeof(out));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break; // float components never reach the integer encoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConvertUnpackRow(const Uint8* src, Uint8* dst, SizeT pixelCount, const UnpackConversionSpec& conv) {
|
||||||
|
const SizeT dstComponentSize = GetShadowComponentSize(conv.internal.component);
|
||||||
|
const SizeT srcComponentSize = conv.isPacked ? 0 : GetBaseTexturePixelDataTypeSize(conv.type);
|
||||||
|
for (SizeT i = 0; i < pixelCount; ++i) {
|
||||||
|
const Uint8* s = src + i * conv.inputPixelSize;
|
||||||
|
Uint8* d = dst + i * conv.internalPixelSize;
|
||||||
|
if (conv.internal.isInteger) {
|
||||||
|
Int64 rgba[4] = {0, 0, 0, 1};
|
||||||
|
if (conv.isPacked) {
|
||||||
|
const Uint32 word = ReadPackedWord(s, conv.packed.totalBits);
|
||||||
|
for (Int ch = 0; ch < 4; ++ch) {
|
||||||
|
const Int pos = conv.mapping.formatPosition[ch];
|
||||||
|
if (pos < 0) continue;
|
||||||
|
Int width = 0;
|
||||||
|
rgba[ch] = ExtractPackedField(word, conv.packed, pos, width);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Int ch = 0; ch < 4; ++ch) {
|
||||||
|
const Int pos = conv.mapping.formatPosition[ch];
|
||||||
|
if (pos < 0) continue;
|
||||||
|
rgba[ch] = DecodeComponentToInt(s + static_cast<SizeT>(pos) * srcComponentSize, conv.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Int ch = 0; ch < conv.internal.channelCount; ++ch) {
|
||||||
|
EncodeShadowComponentInt(d + static_cast<SizeT>(ch) * dstComponentSize,
|
||||||
|
conv.internal.component, rgba[ch]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Float rgba[4] = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
|
if (conv.isPacked) {
|
||||||
|
const Uint32 word = ReadPackedWord(s, conv.packed.totalBits);
|
||||||
|
for (Int ch = 0; ch < 4; ++ch) {
|
||||||
|
const Int pos = conv.mapping.formatPosition[ch];
|
||||||
|
if (pos < 0) continue;
|
||||||
|
Int width = 0;
|
||||||
|
const Uint32 field = ExtractPackedField(word, conv.packed, pos, width);
|
||||||
|
rgba[ch] = static_cast<Float>(field) / static_cast<Float>((1u << width) - 1u);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (Int ch = 0; ch < 4; ++ch) {
|
||||||
|
const Int pos = conv.mapping.formatPosition[ch];
|
||||||
|
if (pos < 0) continue;
|
||||||
|
rgba[ch] =
|
||||||
|
DecodeComponentToFloat(s + static_cast<SizeT>(pos) * srcComponentSize, conv.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (Int ch = 0; ch < conv.internal.channelCount; ++ch) {
|
||||||
|
EncodeShadowComponentFloat(d + static_cast<SizeT>(ch) * dstComponentSize,
|
||||||
|
conv.internal.component, rgba[ch]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
// assume 8 bit per channel
|
// assume 8 bit per channel
|
||||||
// swizzle.size() == channel count
|
// swizzle.size() == channel count
|
||||||
@@ -123,7 +618,12 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
const Int effectiveWidth = (params.RowLength > 0) ? params.RowLength : width;
|
const Int effectiveWidth = (params.RowLength > 0) ? params.RowLength : width;
|
||||||
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);
|
||||||
const SizeT outputRowStride = static_cast<SizeT>(width) * pixelSize;
|
|
||||||
|
UnpackConversionSpec conversion{};
|
||||||
|
const Bool needConversion =
|
||||||
|
!isBitmap && GetUnpackConversionSpec(targetInternalFormat, textureInputFormat, inputDataType, conversion);
|
||||||
|
const SizeT outputPixelSize = needConversion ? conversion.internalPixelSize : pixelSize;
|
||||||
|
const SizeT outputRowStride = static_cast<SizeT>(width) * outputPixelSize;
|
||||||
|
|
||||||
const Int startX = params.SkipPixels;
|
const Int startX = params.SkipPixels;
|
||||||
const Int startY = params.SkipRows;
|
const Int startY = params.SkipRows;
|
||||||
@@ -133,15 +633,16 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
const Int copyHeight = height;
|
const Int copyHeight = height;
|
||||||
const Int copyDepth = depth;
|
const Int copyDepth = depth;
|
||||||
|
|
||||||
MGLOG_D("%s: start at: (%d, %d, %d), copy size: (%d, %d, %d), i/o row stride: (%d, %dx%d)", __func__, startX,
|
MGLOG_D("%s: start at: (%d, %d, %d), copy size: (%d, %d, %d), i/o row stride: (%d, %dx%d), convert: %d",
|
||||||
startY, startZ, copyWidth, copyHeight, copyDepth, inputRowStride, width, pixelSize);
|
__func__, startX, startY, startZ, copyWidth, copyHeight, copyDepth, inputRowStride, width,
|
||||||
|
outputPixelSize, needConversion ? 1 : 0);
|
||||||
|
|
||||||
if (copyWidth <= 0 || copyHeight <= 0 || copyDepth <= 0) {
|
if (copyWidth <= 0 || copyHeight <= 0 || copyDepth <= 0) {
|
||||||
outSize = 0;
|
outSize = 0;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
outSize = static_cast<SizeT>(copyWidth) * copyHeight * copyDepth * pixelSize;
|
outSize = static_cast<SizeT>(copyWidth) * copyHeight * copyDepth * outputPixelSize;
|
||||||
void* outputPixels = malloc(outSize);
|
void* outputPixels = malloc(outSize);
|
||||||
if (!outputPixels) {
|
if (!outputPixels) {
|
||||||
outSize = 0;
|
outSize = 0;
|
||||||
@@ -155,38 +656,42 @@ namespace MobileGL::MG_Util::PixelStoreProcessor {
|
|||||||
src += static_cast<SizeT>(startY) * inputRowStride;
|
src += static_cast<SizeT>(startY) * inputRowStride;
|
||||||
src += static_cast<SizeT>(startX) * pixelSize;
|
src += static_cast<SizeT>(startX) * pixelSize;
|
||||||
|
|
||||||
Bool isByteType =
|
const Bool isByteType =
|
||||||
(inputDataType == TexturePixelDataType::UnsignedByte || inputDataType == TexturePixelDataType::Byte);
|
(inputDataType == TexturePixelDataType::UnsignedByte || inputDataType == TexturePixelDataType::Byte);
|
||||||
Vector<TextureSwizzleParam> colorSwizzle;
|
// UNPACK_SWAP_BYTES applies to the input elements (packed word / component) before conversion.
|
||||||
const Bool needColorSwizzle =
|
const Bool conversionSwapsBytes = needConversion && params.SwapBytes && conversion.swapGroupSize > 1;
|
||||||
targetInternalFormat == TextureInternalFormat::RGBA8 &&
|
Vector<Uint8> swapScratch;
|
||||||
GetRgba8ByteSwizzleForUnpack(textureInputFormat, inputDataType, colorSwizzle);
|
if (conversionSwapsBytes) {
|
||||||
|
swapScratch.resize(static_cast<SizeT>(copyWidth) * pixelSize);
|
||||||
|
}
|
||||||
for (Int z = 0; z < copyDepth; ++z) {
|
for (Int z = 0; z < copyDepth; ++z) {
|
||||||
const Uint8* layerSrc = src;
|
const Uint8* layerSrc = src;
|
||||||
Uint8* layerDst = dst;
|
Uint8* layerDst = dst;
|
||||||
|
|
||||||
for (Int y = 0; y < copyHeight; ++y) {
|
for (Int y = 0; y < copyHeight; ++y) {
|
||||||
Memcpy(layerDst, layerSrc, static_cast<SizeT>(copyWidth) * pixelSize);
|
if (needConversion) {
|
||||||
|
const Uint8* rowSrc = layerSrc;
|
||||||
|
if (conversionSwapsBytes) {
|
||||||
|
Memcpy(swapScratch.data(), layerSrc, static_cast<SizeT>(copyWidth) * pixelSize);
|
||||||
|
const SizeT groupCount = static_cast<SizeT>(copyWidth) * pixelSize / conversion.swapGroupSize;
|
||||||
|
SwapBytes(swapScratch.data(), conversion.swapGroupSize, groupCount);
|
||||||
|
rowSrc = swapScratch.data();
|
||||||
|
}
|
||||||
|
ConvertUnpackRow(rowSrc, layerDst, static_cast<SizeT>(copyWidth), conversion);
|
||||||
|
} else {
|
||||||
|
Memcpy(layerDst, layerSrc, static_cast<SizeT>(copyWidth) * pixelSize);
|
||||||
|
|
||||||
if (params.SwapBytes && pixelSize > 1 && !isByteType) {
|
if (params.SwapBytes && pixelSize > 1 && !isByteType) {
|
||||||
MGLOG_D("%s: SwapBytes", __func__);
|
MGLOG_D("%s: SwapBytes", __func__);
|
||||||
SwapBytes(layerDst, pixelSize, static_cast<SizeT>(copyWidth));
|
SwapBytes(layerDst, pixelSize, static_cast<SizeT>(copyWidth));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.LSBFirst && isBitmap) {
|
if (params.LSBFirst && isBitmap) {
|
||||||
MGLOG_D("%s: LSBFirst", __func__);
|
MGLOG_D("%s: LSBFirst", __func__);
|
||||||
ProcessLSBFirst(layerDst, static_cast<SizeT>(copyWidth), 1);
|
ProcessLSBFirst(layerDst, static_cast<SizeT>(copyWidth), 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needColorSwizzle) {
|
|
||||||
MGLOG_D("%s: Swizzle RGBA8 unpack", __func__);
|
|
||||||
// MGLOG_D("%s: pixel0 before = %x", __func__, *((Uint32*)layerDst));
|
|
||||||
ProcessColorSwizzle(layerDst, static_cast<SizeT>(copyWidth), colorSwizzle);
|
|
||||||
// MGLOG_D("%s: pixel0 after = %x", __func__, *((Uint32*)layerDst));
|
|
||||||
}
|
|
||||||
// else
|
|
||||||
// MGLOG_D("%s: pixel0 = %x", __func__, *((Uint32*)layerDst));
|
|
||||||
|
|
||||||
layerSrc += inputRowStride;
|
layerSrc += inputRowStride;
|
||||||
layerDst += outputRowStride;
|
layerDst += outputRowStride;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user