diff options
Diffstat (limited to 'libs/preview_dds_native/src')
| -rw-r--r-- | libs/preview_dds_native/src/CMakeLists.txt | 17 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddsfile.cpp | 132 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddsfile.h | 47 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddsformat.cpp | 619 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddsformat.h | 200 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddspreview.cpp | 126 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddspreview.h | 44 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddswidget.cpp | 252 | ||||
| -rw-r--r-- | libs/preview_dds_native/src/ddswidget.h | 36 |
9 files changed, 1473 insertions, 0 deletions
diff --git a/libs/preview_dds_native/src/CMakeLists.txt b/libs/preview_dds_native/src/CMakeLists.txt new file mode 100644 index 0000000..c920d49 --- /dev/null +++ b/libs/preview_dds_native/src/CMakeLists.txt @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.16) + +find_package(Qt6 REQUIRED COMPONENTS OpenGL OpenGLWidgets) + +file(GLOB preview_dds_native_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/*.h +) + +add_library(preview_dds_native SHARED ${preview_dds_native_SOURCES}) +mo2_configure_plugin(preview_dds_native NO_SOURCES WARNINGS OFF) +target_link_libraries(preview_dds_native PRIVATE + mo2::uibase + Qt6::OpenGL + Qt6::OpenGLWidgets +) +mo2_install_plugin(preview_dds_native) diff --git a/libs/preview_dds_native/src/ddsfile.cpp b/libs/preview_dds_native/src/ddsfile.cpp new file mode 100644 index 0000000..91dc627 --- /dev/null +++ b/libs/preview_dds_native/src/ddsfile.cpp @@ -0,0 +1,132 @@ +#include "ddsfile.h" + +#include <QFile> + +#include <algorithm> +#include <cstring> + +bool DDSFile::loadFromFile(const QString& path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + return false; + } + return parse(file.readAll()); +} + +bool DDSFile::loadFromData(const QByteArray& data) +{ + return parse(data); +} + +bool DDSFile::parse(const QByteArray& data) +{ + const char* ptr = data.constData(); + int offset = 0; + int size = data.size(); + + // Magic number + if (size < 4) + return false; + uint32_t magic; + std::memcpy(&magic, ptr + offset, 4); + offset += 4; + if (magic != DDS_MAGIC) + return false; + + // DDS header + if (size - offset < static_cast<int>(sizeof(DDSHeader))) + return false; + DDSHeader header; + std::memcpy(&header, ptr + offset, sizeof(DDSHeader)); + offset += sizeof(DDSHeader); + + if (header.dwSize != 124) + return false; + + // DXT10 extended header + DDSHeaderDXT10 dxt10{}; + bool hasDXT10 = false; + if ((header.ddspf.dwFlags & DDPF_FOURCC) && + header.ddspf.dwFourCC == makeFourCC('D', 'X', '1', '0')) { + if (size - offset < static_cast<int>(sizeof(DDSHeaderDXT10))) + return false; + std::memcpy(&dxt10, ptr + offset, sizeof(DDSHeaderDXT10)); + offset += sizeof(DDSHeaderDXT10); + hasDXT10 = true; + } + + m_width = header.dwWidth; + m_height = header.dwHeight; + + // Determine GL format + m_glFormat = getGLFormat(header.ddspf, hasDXT10 ? &dxt10 : nullptr); + if (!m_glFormat.valid) { + return false; + } + + // Determine DXGI format for size calculations + if (hasDXT10) { + m_dxgiFormat = dxt10.dxgiFormat; + } else if (header.ddspf.dwFlags & DDPF_FOURCC) { + m_dxgiFormat = fourCCToDXGI(header.ddspf.dwFourCC); + } else { + m_dxgiFormat = DXGIFormat::UNKNOWN; + } + + // Description + m_description = formatDescription(header.ddspf, hasDXT10 ? &dxt10 : nullptr); + m_description += + QString(" | %1x%2").arg(m_width).arg(m_height); + + // Cubemap detection + m_cubemap = false; + int layers = 1; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP) { + m_cubemap = true; + layers = 0; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEX) layers++; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEX) layers++; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEY) layers++; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEY) layers++; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP_POSITIVEZ) layers++; + if (header.dwCaps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ) layers++; + m_description += " | Cubemap"; + } else { + m_description += " | 2D"; + } + + // Mipmap count + int mipCount = 1; + if (header.dwFlags & DDSD_MIPMAPCOUNT) { + mipCount = std::max(1u, header.dwMipMapCount); + } + if (mipCount > 1) { + m_description += QString(" | %1 mips").arg(mipCount); + } + + // Read pixel data for each face and mip level + m_faces.resize(layers); + for (int face = 0; face < layers; ++face) { + m_faces[face].mips.resize(mipCount); + uint32_t w = m_width; + uint32_t h = m_height; + for (int mip = 0; mip < mipCount; ++mip) { + uint32_t dataSize = mipDataSize(m_dxgiFormat, header.ddspf, w, h); + if (offset + static_cast<int>(dataSize) > size) { + // Truncated file, keep what we have + m_faces[face].mips.resize(mip); + break; + } + m_faces[face].mips[mip].width = w; + m_faces[face].mips[mip].height = h; + m_faces[face].mips[mip].data = + QByteArray(ptr + offset, static_cast<int>(dataSize)); + offset += dataSize; + w = std::max(1u, w / 2); + h = std::max(1u, h / 2); + } + } + + return !m_faces.isEmpty() && !m_faces[0].mips.isEmpty(); +} diff --git a/libs/preview_dds_native/src/ddsfile.h b/libs/preview_dds_native/src/ddsfile.h new file mode 100644 index 0000000..4242c61 --- /dev/null +++ b/libs/preview_dds_native/src/ddsfile.h @@ -0,0 +1,47 @@ +#ifndef DDSFILE_H +#define DDSFILE_H + +#include "ddsformat.h" + +#include <QByteArray> +#include <QString> +#include <QVector> + +struct DDSMipLevel { + uint32_t width; + uint32_t height; + QByteArray data; +}; + +struct DDSFace { + QVector<DDSMipLevel> mips; +}; + +class DDSFile +{ +public: + bool loadFromFile(const QString& path); + bool loadFromData(const QByteArray& data); + + uint32_t width() const { return m_width; } + uint32_t height() const { return m_height; } + bool isCubemap() const { return m_cubemap; } + int faceCount() const { return m_faces.size(); } + int mipCount() const { return m_faces.isEmpty() ? 0 : m_faces[0].mips.size(); } + const DDSFace& face(int i) const { return m_faces[i]; } + const GLFormatInfo& glFormat() const { return m_glFormat; } + QString description() const { return m_description; } + +private: + bool parse(const QByteArray& data); + + uint32_t m_width = 0; + uint32_t m_height = 0; + bool m_cubemap = false; + GLFormatInfo m_glFormat; + DXGIFormat m_dxgiFormat = DXGIFormat::UNKNOWN; + QString m_description; + QVector<DDSFace> m_faces; +}; + +#endif // DDSFILE_H diff --git a/libs/preview_dds_native/src/ddsformat.cpp b/libs/preview_dds_native/src/ddsformat.cpp new file mode 100644 index 0000000..b56db91 --- /dev/null +++ b/libs/preview_dds_native/src/ddsformat.cpp @@ -0,0 +1,619 @@ +#include "ddsformat.h" + +#include <QOpenGLFunctions> +#include <QString> + +#include <algorithm> +#include <cstring> + +// GL compressed format constants not always in headers +#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT +#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT +#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT +#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2 +#endif +#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT +#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3 +#endif +#ifndef GL_COMPRESSED_RED_RGTC1 +#define GL_COMPRESSED_RED_RGTC1 0x8DBB +#endif +#ifndef GL_COMPRESSED_SIGNED_RED_RGTC1 +#define GL_COMPRESSED_SIGNED_RED_RGTC1 0x8DBC +#endif +#ifndef GL_COMPRESSED_RG_RGTC2 +#define GL_COMPRESSED_RG_RGTC2 0x8DBD +#endif +#ifndef GL_COMPRESSED_SIGNED_RG_RGTC2 +#define GL_COMPRESSED_SIGNED_RG_RGTC2 0x8DBE +#endif +#ifndef GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT +#define GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT 0x8E8F +#endif +#ifndef GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT +#define GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT 0x8E8E +#endif +#ifndef GL_COMPRESSED_RGBA_BPTC_UNORM +#define GL_COMPRESSED_RGBA_BPTC_UNORM 0x8E8C +#endif +#ifndef GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM +#define GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM 0x8E8D +#endif +#ifndef GL_COMPRESSED_SRGB_S3TC_DXT1_EXT +#define GL_COMPRESSED_SRGB_S3TC_DXT1_EXT 0x8C4C +#endif +#ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT 0x8C4D +#endif +#ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT 0x8C4E +#endif +#ifndef GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT +#define GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT 0x8C4F +#endif + +static GLFormatInfo dxgiToGL(DXGIFormat fmt) +{ + GLFormatInfo info; + info.valid = true; + + switch (fmt) { + // BC1 (DXT1) + case DXGIFormat::BC1_UNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT1_EXT; + break; + case DXGIFormat::BC1_UNORM_SRGB: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT; + break; + // BC2 (DXT3) + case DXGIFormat::BC2_UNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT3_EXT; + break; + case DXGIFormat::BC2_UNORM_SRGB: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT; + break; + // BC3 (DXT5) + case DXGIFormat::BC3_UNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RGBA_S3TC_DXT5_EXT; + break; + case DXGIFormat::BC3_UNORM_SRGB: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT; + break; + // BC4 + case DXGIFormat::BC4_UNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RED_RGTC1; + break; + case DXGIFormat::BC4_SNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_SIGNED_RED_RGTC1; + break; + // BC5 + case DXGIFormat::BC5_UNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RG_RGTC2; + break; + case DXGIFormat::BC5_SNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_SIGNED_RG_RGTC2; + break; + // BC6H + case DXGIFormat::BC6H_UF16: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT; + break; + case DXGIFormat::BC6H_SF16: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT; + break; + // BC7 + case DXGIFormat::BC7_UNORM: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_RGBA_BPTC_UNORM; + break; + case DXGIFormat::BC7_UNORM_SRGB: + info.compressed = true; + info.internalFormat = GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM; + break; + + // Uncompressed RGBA + case DXGIFormat::R8G8B8A8_UNORM: + case DXGIFormat::R8G8B8A8_UNORM_SRGB: + info.internalFormat = GL_RGBA8; + info.format = GL_RGBA; + info.type = GL_UNSIGNED_BYTE; + break; + case DXGIFormat::R8G8B8A8_UINT: + info.internalFormat = GL_RGBA8UI; + info.format = GL_RGBA_INTEGER; + info.type = GL_UNSIGNED_BYTE; + info.sampler = SamplerType::UInt; + break; + case DXGIFormat::R8G8B8A8_SNORM: + info.internalFormat = GL_RGBA8_SNORM; + info.format = GL_RGBA; + info.type = GL_BYTE; + break; + case DXGIFormat::R8G8B8A8_SINT: + info.internalFormat = GL_RGBA8I; + info.format = GL_RGBA_INTEGER; + info.type = GL_BYTE; + info.sampler = SamplerType::SInt; + break; + + // BGRA + case DXGIFormat::B8G8R8A8_UNORM: + case DXGIFormat::B8G8R8A8_UNORM_SRGB: + info.internalFormat = GL_RGBA8; + info.format = GL_BGRA; + info.type = GL_UNSIGNED_BYTE; + break; + case DXGIFormat::B8G8R8X8_UNORM: + case DXGIFormat::B8G8R8X8_UNORM_SRGB: + info.internalFormat = GL_RGBA8; + info.format = GL_BGRA; + info.type = GL_UNSIGNED_BYTE; + break; + + // 16-bit float + case DXGIFormat::R16G16B16A16_FLOAT: + info.internalFormat = GL_RGBA16F; + info.format = GL_RGBA; + info.type = GL_HALF_FLOAT; + break; + case DXGIFormat::R16G16B16A16_UNORM: + info.internalFormat = GL_RGBA16; + info.format = GL_RGBA; + info.type = GL_UNSIGNED_SHORT; + break; + case DXGIFormat::R16G16B16A16_UINT: + info.internalFormat = GL_RGBA16UI; + info.format = GL_RGBA_INTEGER; + info.type = GL_UNSIGNED_SHORT; + info.sampler = SamplerType::UInt; + break; + case DXGIFormat::R16G16B16A16_SNORM: + info.internalFormat = GL_RGBA16_SNORM; + info.format = GL_RGBA; + info.type = GL_SHORT; + break; + case DXGIFormat::R16G16B16A16_SINT: + info.internalFormat = GL_RGBA16I; + info.format = GL_RGBA_INTEGER; + info.type = GL_SHORT; + info.sampler = SamplerType::SInt; + break; + + // 32-bit float + case DXGIFormat::R32G32B32A32_FLOAT: + info.internalFormat = GL_RGBA32F; + info.format = GL_RGBA; + info.type = GL_FLOAT; + break; + case DXGIFormat::R32G32B32_FLOAT: + info.internalFormat = GL_RGB32F; + info.format = GL_RGB; + info.type = GL_FLOAT; + break; + + // RG formats + case DXGIFormat::R8G8_UNORM: + info.internalFormat = GL_RG8; + info.format = GL_RG; + info.type = GL_UNSIGNED_BYTE; + break; + case DXGIFormat::R16G16_FLOAT: + info.internalFormat = GL_RG16F; + info.format = GL_RG; + info.type = GL_HALF_FLOAT; + break; + case DXGIFormat::R16G16_UNORM: + info.internalFormat = GL_RG16; + info.format = GL_RG; + info.type = GL_UNSIGNED_SHORT; + break; + case DXGIFormat::R32G32_FLOAT: + info.internalFormat = GL_RG32F; + info.format = GL_RG; + info.type = GL_FLOAT; + break; + + // R formats + case DXGIFormat::R8_UNORM: + info.internalFormat = GL_R8; + info.format = GL_RED; + info.type = GL_UNSIGNED_BYTE; + break; + case DXGIFormat::R16_FLOAT: + info.internalFormat = GL_R16F; + info.format = GL_RED; + info.type = GL_HALF_FLOAT; + break; + case DXGIFormat::R16_UNORM: + info.internalFormat = GL_R16; + info.format = GL_RED; + info.type = GL_UNSIGNED_SHORT; + break; + case DXGIFormat::R32_FLOAT: + info.internalFormat = GL_R32F; + info.format = GL_RED; + info.type = GL_FLOAT; + break; + case DXGIFormat::A8_UNORM: + info.internalFormat = GL_R8; + info.format = GL_RED; + info.type = GL_UNSIGNED_BYTE; + break; + + // Packed formats + case DXGIFormat::R10G10B10A2_UNORM: + info.internalFormat = GL_RGB10_A2; + info.format = GL_RGBA; + info.type = GL_UNSIGNED_INT_2_10_10_10_REV; + break; + case DXGIFormat::R11G11B10_FLOAT: + info.internalFormat = GL_R11F_G11F_B10F; + info.format = GL_RGB; + info.type = GL_UNSIGNED_INT_10F_11F_11F_REV; + break; + case DXGIFormat::B5G6R5_UNORM: + info.internalFormat = GL_RGB565; + info.format = GL_RGB; + info.type = GL_UNSIGNED_SHORT_5_6_5; + break; + case DXGIFormat::B5G5R5A1_UNORM: + info.internalFormat = GL_RGB5_A1; + info.format = GL_BGRA; + info.type = GL_UNSIGNED_SHORT_1_5_5_5_REV; + break; + case DXGIFormat::B4G4R4A4_UNORM: + info.internalFormat = GL_RGBA4; + info.format = GL_BGRA; + info.type = GL_UNSIGNED_SHORT_4_4_4_4_REV; + break; + + default: + info.valid = false; + break; + } + + return info; +} + +DXGIFormat fourCCToDXGI(uint32_t fourCC) +{ + if (fourCC == makeFourCC('D', 'X', 'T', '1')) + return DXGIFormat::BC1_UNORM; + if (fourCC == makeFourCC('D', 'X', 'T', '3')) + return DXGIFormat::BC2_UNORM; + if (fourCC == makeFourCC('D', 'X', 'T', '5')) + return DXGIFormat::BC3_UNORM; + if (fourCC == makeFourCC('B', 'C', '4', 'U') || + fourCC == makeFourCC('A', 'T', 'I', '1')) + return DXGIFormat::BC4_UNORM; + if (fourCC == makeFourCC('B', 'C', '4', 'S')) + return DXGIFormat::BC4_SNORM; + if (fourCC == makeFourCC('A', 'T', 'I', '2') || + fourCC == makeFourCC('B', 'C', '5', 'U')) + return DXGIFormat::BC5_UNORM; + if (fourCC == makeFourCC('B', 'C', '5', 'S')) + return DXGIFormat::BC5_SNORM; + + // Numeric FourCC codes for float/half-float formats + switch (fourCC) { + case 36: + return DXGIFormat::R16G16B16A16_UNORM; + case 110: + return DXGIFormat::R16G16B16A16_SNORM; + case 111: + return DXGIFormat::R16_FLOAT; + case 112: + return DXGIFormat::R16G16_FLOAT; + case 113: + return DXGIFormat::R16G16B16A16_FLOAT; + case 114: + return DXGIFormat::R32_FLOAT; + case 115: + return DXGIFormat::R32G32_FLOAT; + case 116: + return DXGIFormat::R32G32B32A32_FLOAT; + default: + return DXGIFormat::UNKNOWN; + } +} + +// Helper: count trailing zeros / bit shift for a mask +static int maskShift(uint32_t mask) +{ + if (mask == 0) + return 0; + int shift = 0; + while ((mask & 1) == 0) { + mask >>= 1; + ++shift; + } + return shift; +} + +static int maskBits(uint32_t mask) +{ + int count = 0; + while (mask) { + count += mask & 1; + mask >>= 1; + } + return count; +} + +// Build a converter for arbitrary bitmask pixel formats +static GLFormatInfo buildBitmaskFormat(const DDSPixelFormat& pf) +{ + GLFormatInfo info; + info.valid = true; + + int rShift = maskShift(pf.dwRBitMask); + int gShift = maskShift(pf.dwGBitMask); + int bShift = maskShift(pf.dwBBitMask); + int aShift = maskShift(pf.dwABitMask); + int rBits = maskBits(pf.dwRBitMask); + int gBits = maskBits(pf.dwGBitMask); + int bBits = maskBits(pf.dwBBitMask); + int aBits = maskBits(pf.dwABitMask); + int bpp = pf.dwRGBBitCount; + + // Try to match common uncompressed formats directly + if (bpp == 32 && pf.dwRBitMask == 0x000000FF && pf.dwGBitMask == 0x0000FF00 && + pf.dwBBitMask == 0x00FF0000 && + (pf.dwABitMask == 0xFF000000 || pf.dwABitMask == 0)) { + info.internalFormat = GL_RGBA8; + info.format = GL_RGBA; + info.type = GL_UNSIGNED_BYTE; + return info; + } + if (bpp == 32 && pf.dwRBitMask == 0x00FF0000 && pf.dwGBitMask == 0x0000FF00 && + pf.dwBBitMask == 0x000000FF) { + info.internalFormat = GL_RGBA8; + info.format = GL_BGRA; + info.type = GL_UNSIGNED_BYTE; + return info; + } + + // Generic converter: extract channels and pack into RGBA8 + uint32_t rMask = pf.dwRBitMask; + uint32_t gMask = pf.dwGBitMask; + uint32_t bMask = pf.dwBBitMask; + uint32_t aMask = pf.dwABitMask; + bool hasAlpha = (pf.dwFlags & DDPF_ALPHAPIXELS) && aMask != 0; + int bytesPerPixel = bpp / 8; + + info.internalFormat = GL_RGBA8; + info.format = GL_RGBA; + info.type = GL_UNSIGNED_BYTE; + info.converter = [=](const QByteArray& data, int w, int h) -> QByteArray { + QByteArray result(w * h * 4, '\0'); + const uint8_t* src = reinterpret_cast<const uint8_t*>(data.constData()); + uint8_t* dst = reinterpret_cast<uint8_t*>(result.data()); + + for (int i = 0; i < w * h; ++i) { + uint32_t pixel = 0; + std::memcpy(&pixel, src + i * bytesPerPixel, + std::min(bytesPerPixel, 4)); + + int r = rBits > 0 ? ((pixel & rMask) >> rShift) * 255 / ((1 << rBits) - 1) : 0; + int g = gBits > 0 ? ((pixel & gMask) >> gShift) * 255 / ((1 << gBits) - 1) : 0; + int b = bBits > 0 ? ((pixel & bMask) >> bShift) * 255 / ((1 << bBits) - 1) : 0; + int a = hasAlpha && aBits > 0 + ? ((pixel & aMask) >> aShift) * 255 / ((1 << aBits) - 1) + : 255; + + dst[i * 4 + 0] = static_cast<uint8_t>(r); + dst[i * 4 + 1] = static_cast<uint8_t>(g); + dst[i * 4 + 2] = static_cast<uint8_t>(b); + dst[i * 4 + 3] = static_cast<uint8_t>(a); + } + return result; + }; + + return info; +} + +GLFormatInfo getGLFormat(const DDSPixelFormat& pf, const DDSHeaderDXT10* dxt10) +{ + // DX10 extended header takes priority + if (dxt10) { + return dxgiToGL(dxt10->dxgiFormat); + } + + // FourCC compressed or float formats + if (pf.dwFlags & DDPF_FOURCC) { + DXGIFormat dxgi = fourCCToDXGI(pf.dwFourCC); + if (dxgi != DXGIFormat::UNKNOWN) { + return dxgiToGL(dxgi); + } + GLFormatInfo info; + info.valid = false; + return info; + } + + // Uncompressed with bitmasks + if (pf.dwFlags & (DDPF_RGB | DDPF_LUMINANCE | DDPF_YUV | DDPF_ALPHA)) { + return buildBitmaskFormat(pf); + } + + GLFormatInfo info; + info.valid = false; + return info; +} + +static bool isBlockCompressed(DXGIFormat fmt) +{ + switch (fmt) { + case DXGIFormat::BC1_UNORM: + case DXGIFormat::BC1_UNORM_SRGB: + case DXGIFormat::BC2_UNORM: + case DXGIFormat::BC2_UNORM_SRGB: + case DXGIFormat::BC3_UNORM: + case DXGIFormat::BC3_UNORM_SRGB: + case DXGIFormat::BC4_UNORM: + case DXGIFormat::BC4_SNORM: + case DXGIFormat::BC5_UNORM: + case DXGIFormat::BC5_SNORM: + case DXGIFormat::BC6H_UF16: + case DXGIFormat::BC6H_SF16: + case DXGIFormat::BC7_UNORM: + case DXGIFormat::BC7_UNORM_SRGB: + return true; + default: + return false; + } +} + +static int blockSize(DXGIFormat fmt) +{ + switch (fmt) { + case DXGIFormat::BC1_UNORM: + case DXGIFormat::BC1_UNORM_SRGB: + case DXGIFormat::BC4_UNORM: + case DXGIFormat::BC4_SNORM: + return 8; + default: + return 16; + } +} + +uint32_t mipDataSize(DXGIFormat fmt, const DDSPixelFormat& pf, + uint32_t width, uint32_t height) +{ + if (isBlockCompressed(fmt)) { + uint32_t blocksW = std::max(1u, (width + 3) / 4); + uint32_t blocksH = std::max(1u, (height + 3) / 4); + return blocksW * blocksH * blockSize(fmt); + } + + // Uncompressed: use bits per pixel + uint32_t bpp = pf.dwRGBBitCount; + if (bpp == 0) { + // Estimate from DXGI format for non-bitmask formats + switch (fmt) { + case DXGIFormat::R32G32B32A32_FLOAT: + bpp = 128; + break; + case DXGIFormat::R32G32B32_FLOAT: + bpp = 96; + break; + case DXGIFormat::R16G16B16A16_FLOAT: + case DXGIFormat::R16G16B16A16_UNORM: + case DXGIFormat::R16G16B16A16_SNORM: + case DXGIFormat::R32G32_FLOAT: + bpp = 64; + break; + case DXGIFormat::R8G8B8A8_UNORM: + case DXGIFormat::R8G8B8A8_UNORM_SRGB: + case DXGIFormat::B8G8R8A8_UNORM: + case DXGIFormat::B8G8R8X8_UNORM: + case DXGIFormat::R16G16_FLOAT: + case DXGIFormat::R16G16_UNORM: + case DXGIFormat::R32_FLOAT: + case DXGIFormat::R10G10B10A2_UNORM: + case DXGIFormat::R11G11B10_FLOAT: + bpp = 32; + break; + case DXGIFormat::R8G8_UNORM: + case DXGIFormat::R16_FLOAT: + case DXGIFormat::R16_UNORM: + case DXGIFormat::B5G6R5_UNORM: + case DXGIFormat::B5G5R5A1_UNORM: + case DXGIFormat::B4G4R4A4_UNORM: + bpp = 16; + break; + case DXGIFormat::R8_UNORM: + case DXGIFormat::A8_UNORM: + bpp = 8; + break; + default: + bpp = 32; + break; + } + } + return width * height * bpp / 8; +} + +static const char* dxgiFormatName(DXGIFormat fmt) +{ + switch (fmt) { + case DXGIFormat::BC1_UNORM: + return "BC1_UNORM (DXT1)"; + case DXGIFormat::BC1_UNORM_SRGB: + return "BC1_UNORM_SRGB"; + case DXGIFormat::BC2_UNORM: + return "BC2_UNORM (DXT3)"; + case DXGIFormat::BC2_UNORM_SRGB: + return "BC2_UNORM_SRGB"; + case DXGIFormat::BC3_UNORM: + return "BC3_UNORM (DXT5)"; + case DXGIFormat::BC3_UNORM_SRGB: + return "BC3_UNORM_SRGB"; + case DXGIFormat::BC4_UNORM: + return "BC4_UNORM"; + case DXGIFormat::BC4_SNORM: + return "BC4_SNORM"; + case DXGIFormat::BC5_UNORM: + return "BC5_UNORM"; + case DXGIFormat::BC5_SNORM: + return "BC5_SNORM"; + case DXGIFormat::BC6H_UF16: + return "BC6H_UF16"; + case DXGIFormat::BC6H_SF16: + return "BC6H_SF16"; + case DXGIFormat::BC7_UNORM: + return "BC7_UNORM"; + case DXGIFormat::BC7_UNORM_SRGB: + return "BC7_UNORM_SRGB"; + case DXGIFormat::R8G8B8A8_UNORM: + return "R8G8B8A8_UNORM"; + case DXGIFormat::R8G8B8A8_UNORM_SRGB: + return "R8G8B8A8_UNORM_SRGB"; + case DXGIFormat::B8G8R8A8_UNORM: + return "B8G8R8A8_UNORM"; + case DXGIFormat::R16G16B16A16_FLOAT: + return "R16G16B16A16_FLOAT"; + case DXGIFormat::R32G32B32A32_FLOAT: + return "R32G32B32A32_FLOAT"; + default: + return "Unknown"; + } +} + +QString formatDescription(const DDSPixelFormat& pf, const DDSHeaderDXT10* dxt10) +{ + if (dxt10) { + return QString("DXGI: %1").arg(dxgiFormatName(dxt10->dxgiFormat)); + } + + if (pf.dwFlags & DDPF_FOURCC) { + char cc[5] = {0}; + std::memcpy(cc, &pf.dwFourCC, 4); + DXGIFormat dxgi = fourCCToDXGI(pf.dwFourCC); + if (dxgi != DXGIFormat::UNKNOWN) { + return QString("FourCC: %1 (%2)").arg(cc).arg(dxgiFormatName(dxgi)); + } + return QString("FourCC: %1").arg(cc); + } + + return QString("%1-bit R:0x%2 G:0x%3 B:0x%4 A:0x%5") + .arg(pf.dwRGBBitCount) + .arg(pf.dwRBitMask, 0, 16) + .arg(pf.dwGBitMask, 0, 16) + .arg(pf.dwBBitMask, 0, 16) + .arg(pf.dwABitMask, 0, 16); +} diff --git a/libs/preview_dds_native/src/ddsformat.h b/libs/preview_dds_native/src/ddsformat.h new file mode 100644 index 0000000..22d3565 --- /dev/null +++ b/libs/preview_dds_native/src/ddsformat.h @@ -0,0 +1,200 @@ +#ifndef DDSFORMAT_H +#define DDSFORMAT_H + +#include <cstdint> +#include <functional> +#include <vector> + +#include <QByteArray> + +// DDS file magic number +constexpr uint32_t DDS_MAGIC = 0x20534444; // "DDS " + +// DDS_PIXELFORMAT flags +constexpr uint32_t DDPF_ALPHAPIXELS = 0x1; +constexpr uint32_t DDPF_ALPHA = 0x2; +constexpr uint32_t DDPF_FOURCC = 0x4; +constexpr uint32_t DDPF_RGB = 0x40; +constexpr uint32_t DDPF_YUV = 0x200; +constexpr uint32_t DDPF_LUMINANCE = 0x20000; + +// DDS_HEADER flags +constexpr uint32_t DDSD_CAPS = 0x1; +constexpr uint32_t DDSD_HEIGHT = 0x2; +constexpr uint32_t DDSD_WIDTH = 0x4; +constexpr uint32_t DDSD_PITCH = 0x8; +constexpr uint32_t DDSD_PIXELFORMAT = 0x1000; +constexpr uint32_t DDSD_MIPMAPCOUNT = 0x20000; +constexpr uint32_t DDSD_LINEARSIZE = 0x80000; +constexpr uint32_t DDSD_DEPTH = 0x800000; + +// DDS_HEADER caps2 +constexpr uint32_t DDSCAPS2_CUBEMAP = 0x200; +constexpr uint32_t DDSCAPS2_CUBEMAP_POSITIVEX = 0x400; +constexpr uint32_t DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800; +constexpr uint32_t DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000; +constexpr uint32_t DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000; +constexpr uint32_t DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000; +constexpr uint32_t DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; +constexpr uint32_t DDSCAPS2_VOLUME = 0x200000; + +constexpr uint32_t DDSCAPS2_CUBEMAP_ALLFACES = + DDSCAPS2_CUBEMAP_POSITIVEX | DDSCAPS2_CUBEMAP_NEGATIVEX | + DDSCAPS2_CUBEMAP_POSITIVEY | DDSCAPS2_CUBEMAP_NEGATIVEY | + DDSCAPS2_CUBEMAP_POSITIVEZ | DDSCAPS2_CUBEMAP_NEGATIVEZ; + +// DXGI formats (subset covering common DDS textures) +enum class DXGIFormat : uint32_t { + UNKNOWN = 0, + R32G32B32A32_FLOAT = 2, + R32G32B32A32_UINT = 3, + R32G32B32A32_SINT = 4, + R32G32B32_FLOAT = 6, + R32G32B32_UINT = 7, + R32G32B32_SINT = 8, + R16G16B16A16_FLOAT = 10, + R16G16B16A16_UNORM = 11, + R16G16B16A16_UINT = 12, + R16G16B16A16_SNORM = 13, + R16G16B16A16_SINT = 14, + R32G32_FLOAT = 16, + R32G32_UINT = 17, + R32G32_SINT = 18, + R10G10B10A2_UNORM = 24, + R10G10B10A2_UINT = 25, + R11G11B10_FLOAT = 26, + R8G8B8A8_UNORM = 28, + R8G8B8A8_UNORM_SRGB = 29, + R8G8B8A8_UINT = 30, + R8G8B8A8_SNORM = 31, + R8G8B8A8_SINT = 32, + R16G16_FLOAT = 34, + R16G16_UNORM = 35, + R16G16_UINT = 36, + R16G16_SNORM = 37, + R16G16_SINT = 38, + R32_FLOAT = 41, + R32_UINT = 42, + R32_SINT = 43, + R8G8_UNORM = 49, + R8G8_UINT = 50, + R8G8_SNORM = 51, + R8G8_SINT = 52, + R16_FLOAT = 54, + R16_UNORM = 56, + R16_UINT = 57, + R16_SNORM = 58, + R16_SINT = 59, + R8_UNORM = 61, + R8_UINT = 62, + R8_SNORM = 63, + R8_SINT = 64, + A8_UNORM = 65, + BC1_UNORM = 71, + BC1_UNORM_SRGB = 72, + BC2_UNORM = 74, + BC2_UNORM_SRGB = 75, + BC3_UNORM = 77, + BC3_UNORM_SRGB = 78, + BC4_UNORM = 80, + BC4_SNORM = 81, + BC5_UNORM = 83, + BC5_SNORM = 84, + B5G6R5_UNORM = 85, + B5G5R5A1_UNORM = 86, + B8G8R8A8_UNORM = 87, + B8G8R8X8_UNORM = 88, + B8G8R8A8_UNORM_SRGB = 91, + B8G8R8X8_UNORM_SRGB = 93, + BC6H_UF16 = 95, + BC6H_SF16 = 96, + BC7_UNORM = 98, + BC7_UNORM_SRGB = 99, + B4G4R4A4_UNORM = 115, +}; + +// D3D10 resource dimension +enum class D3D10ResourceDimension : uint32_t { + Unknown = 0, + Buffer = 1, + Texture1D = 2, + Texture2D = 3, + Texture3D = 4, +}; + +#pragma pack(push, 1) + +struct DDSPixelFormat { + uint32_t dwSize; + uint32_t dwFlags; + uint32_t dwFourCC; + uint32_t dwRGBBitCount; + uint32_t dwRBitMask; + uint32_t dwGBitMask; + uint32_t dwBBitMask; + uint32_t dwABitMask; +}; + +struct DDSHeader { + uint32_t dwSize; + uint32_t dwFlags; + uint32_t dwHeight; + uint32_t dwWidth; + uint32_t dwPitchOrLinearSize; + uint32_t dwDepth; + uint32_t dwMipMapCount; + uint32_t dwReserved1[11]; + DDSPixelFormat ddspf; + uint32_t dwCaps; + uint32_t dwCaps2; + uint32_t dwCaps3; + uint32_t dwCaps4; + uint32_t dwReserved2; +}; + +struct DDSHeaderDXT10 { + DXGIFormat dxgiFormat; + D3D10ResourceDimension resourceDimension; + uint32_t miscFlag; + uint32_t arraySize; + uint32_t miscFlags2; +}; + +#pragma pack(pop) + +// Sampler type for shader selection +enum class SamplerType { Float, UInt, SInt }; + +// OpenGL format info +struct GLFormatInfo { + bool valid = false; + bool compressed = false; + uint32_t internalFormat = 0; + uint32_t format = 0; // only for uncompressed + uint32_t type = 0; // only for uncompressed + SamplerType sampler = SamplerType::Float; + // Optional converter for non-standard bitmask formats + std::function<QByteArray(const QByteArray&, int, int)> converter; +}; + +// Inline helper: make a FourCC from 4 chars +constexpr uint32_t makeFourCC(char a, char b, char c, char d) +{ + return static_cast<uint32_t>(a) | (static_cast<uint32_t>(b) << 8) | + (static_cast<uint32_t>(c) << 16) | (static_cast<uint32_t>(d) << 24); +} + +// Convert a FourCC code to DXGI format +DXGIFormat fourCCToDXGI(uint32_t fourCC); + +// Resolve the OpenGL format from a DDS pixel format + optional DXT10 header +GLFormatInfo getGLFormat(const DDSPixelFormat& pf, const DDSHeaderDXT10* dxt10); + +// Calculate mip level data size in bytes +uint32_t mipDataSize(DXGIFormat fmt, const DDSPixelFormat& pf, + uint32_t width, uint32_t height); + +// Get a human-readable format description +QString formatDescription(const DDSPixelFormat& pf, const DDSHeaderDXT10* dxt10); + +#endif // DDSFORMAT_H diff --git a/libs/preview_dds_native/src/ddspreview.cpp b/libs/preview_dds_native/src/ddspreview.cpp new file mode 100644 index 0000000..2bb95be --- /dev/null +++ b/libs/preview_dds_native/src/ddspreview.cpp @@ -0,0 +1,126 @@ +#include "ddspreview.h" +#include "ddsfile.h" +#include "ddswidget.h" + +#include <QLabel> +#include <QVBoxLayout> +#include <QWidget> + +using namespace MOBase; + +DDSPreview::DDSPreview() {} + +bool DDSPreview::init(IOrganizer* moInfo) +{ + m_organizer = moInfo; + return true; +} + +QString DDSPreview::name() const +{ + return "DDS Preview (Native)"; +} + +QString DDSPreview::localizedName() const +{ + return tr("DDS Preview (Native)"); +} + +QString DDSPreview::author() const +{ + return "AnyOldName3"; +} + +QString DDSPreview::description() const +{ + return tr("Displays DDS texture files using OpenGL."); +} + +VersionInfo DDSPreview::version() const +{ + return VersionInfo(1, 0, 0, VersionInfo::RELEASE_FINAL); +} + +QList<PluginSetting> DDSPreview::settings() const +{ + return {}; +} + +std::set<QString> DDSPreview::supportedExtensions() const +{ + return {"dds"}; +} + +bool DDSPreview::supportsArchives() const +{ + return true; +} + +QWidget* DDSPreview::genFilePreview(const QString& fileName, + const QSize& maxSize) const +{ + DDSFile dds; + if (!dds.loadFromFile(fileName)) { + QLabel* label = new QLabel(tr("Failed to load DDS file.")); + label->setAlignment(Qt::AlignCenter); + return label; + } + return buildPreview(dds, maxSize); +} + +QWidget* DDSPreview::genDataPreview(const QByteArray& fileData, + const QString& fileName, + const QSize& maxSize) const +{ + DDSFile dds; + if (!dds.loadFromData(fileData)) { + QLabel* label = new QLabel(tr("Failed to load DDS data.")); + label->setAlignment(Qt::AlignCenter); + return label; + } + return buildPreview(dds, maxSize); +} + +QWidget* DDSPreview::buildPreview(DDSFile& dds, const QSize& maxSize) const +{ + QWidget* container = new QWidget(); + QVBoxLayout* layout = new QVBoxLayout(container); + + // Description label + QLabel* descLabel = new QLabel(dds.description()); + descLabel->setAlignment(Qt::AlignCenter); + layout->addWidget(descLabel); + + // OpenGL preview widget — we need to keep the DDSFile alive, so store + // it in the container. We use a shared_ptr stored as a property. + auto* ddsPtr = new DDSFile(std::move(dds)); + DDSWidget* widget = new DDSWidget(*ddsPtr, container); + + // Clean up DDSFile when container is destroyed + QObject::connect(container, &QObject::destroyed, [ddsPtr]() { + delete ddsPtr; + }); + + // Size the widget proportionally + int previewW = maxSize.width(); + int previewH = maxSize.height() - 30; // leave room for label + if (ddsPtr->width() > 0 && ddsPtr->height() > 0) { + float texAspect = + static_cast<float>(ddsPtr->width()) / ddsPtr->height(); + float availAspect = + static_cast<float>(previewW) / std::max(1, previewH); + if (texAspect > availAspect) { + previewH = static_cast<int>(previewW / texAspect); + } else { + previewW = static_cast<int>(previewH * texAspect); + } + } + widget->setMinimumSize(std::min(previewW, static_cast<int>(ddsPtr->width())), + std::min(previewH, static_cast<int>(ddsPtr->height()))); + widget->setMaximumSize(previewW, previewH); + + layout->addWidget(widget); + container->setLayout(layout); + + return container; +} diff --git a/libs/preview_dds_native/src/ddspreview.h b/libs/preview_dds_native/src/ddspreview.h new file mode 100644 index 0000000..36f2bec --- /dev/null +++ b/libs/preview_dds_native/src/ddspreview.h @@ -0,0 +1,44 @@ +#ifndef DDSPREVIEW_H +#define DDSPREVIEW_H + +#include <set> + +#include <QString> + +#include <uibase/iplugin.h> +#include <uibase/ipluginpreview.h> + +class DDSPreview : public MOBase::IPluginPreview +{ + Q_OBJECT + Q_INTERFACES(MOBase::IPlugin MOBase::IPluginPreview) + Q_PLUGIN_METADATA(IID "org.tannin.DDSPreviewNative") + +public: + DDSPreview(); + +public: // IPlugin + bool init(MOBase::IOrganizer* moInfo) override; + QString name() const override; + QString localizedName() const override; + QString author() const override; + QString description() const override; + MOBase::VersionInfo version() const override; + QList<MOBase::PluginSetting> settings() const override; + +public: // IPluginPreview + std::set<QString> supportedExtensions() const override; + QWidget* genFilePreview(const QString& fileName, + const QSize& maxSize) const override; + bool supportsArchives() const override; + QWidget* genDataPreview(const QByteArray& fileData, + const QString& fileName, + const QSize& maxSize) const override; + +private: + QWidget* buildPreview(class DDSFile& dds, const QSize& maxSize) const; + + MOBase::IOrganizer* m_organizer = nullptr; +}; + +#endif // DDSPREVIEW_H diff --git a/libs/preview_dds_native/src/ddswidget.cpp b/libs/preview_dds_native/src/ddswidget.cpp new file mode 100644 index 0000000..43eb586 --- /dev/null +++ b/libs/preview_dds_native/src/ddswidget.cpp @@ -0,0 +1,252 @@ +#include "ddswidget.h" + +#include <QDebug> + +#include <algorithm> +#include <cstring> + +// Vertex data: position (x,y) + texcoord (u,v) +static const float quadVertices[] = { + // pos // tex + -1.0f, -1.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 1.0f, 0.0f, +}; + +static const char* vertexShaderSrc = R"( +#version 330 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aTexCoord; +out vec2 vTexCoord; +uniform float uAspect; +uniform float uWidgetAspect; +void main() { + vec2 pos = aPos; + float ratio = uAspect / uWidgetAspect; + if (ratio > 1.0) + pos.y *= 1.0 / ratio; + else + pos.x *= ratio; + gl_Position = vec4(pos, 0.0, 1.0); + vTexCoord = aTexCoord; +} +)"; + +static const char* fragmentShaderSrc = R"( +#version 330 core +in vec2 vTexCoord; +out vec4 fragColor; +uniform sampler2D uTexture; +void main() { + vec4 texel = texture(uTexture, vTexCoord); + // Checkerboard for transparency + vec2 checker = floor(vTexCoord * 16.0); + float c = mod(checker.x + checker.y, 2.0); + vec3 bg = mix(vec3(0.6), vec3(0.4), c); + fragColor = vec4(mix(bg, texel.rgb, texel.a), 1.0); +} +)"; + +static const char* cubemapFragSrc = R"( +#version 330 core +in vec2 vTexCoord; +out vec4 fragColor; +uniform samplerCube uTexture; +void main() { + // Spherical projection for cubemap preview + float theta = vTexCoord.x * 6.28318530718; + float phi = vTexCoord.y * 3.14159265359; + vec3 dir = vec3(sin(phi) * cos(theta), cos(phi), sin(phi) * sin(theta)); + vec4 texel = texture(uTexture, dir); + vec2 checker = floor(vTexCoord * 16.0); + float c = mod(checker.x + checker.y, 2.0); + vec3 bg = mix(vec3(0.6), vec3(0.4), c); + fragColor = vec4(mix(bg, texel.rgb, texel.a), 1.0); +} +)"; + +DDSWidget::DDSWidget(const DDSFile& dds, QWidget* parent) + : QOpenGLWidget(parent), m_dds(dds), m_vbo(QOpenGLBuffer::VertexBuffer) +{ + if (dds.height() > 0) { + m_aspectRatio = + static_cast<float>(dds.width()) / static_cast<float>(dds.height()); + } +} + +DDSWidget::~DDSWidget() +{ + makeCurrent(); + delete m_texture; + delete m_shader; + m_vbo.destroy(); + doneCurrent(); +} + +void DDSWidget::initializeGL() +{ + initializeOpenGLFunctions(); + + glClearColor(0.2f, 0.2f, 0.2f, 1.0f); + + // VBO + m_vbo.create(); + m_vbo.bind(); + m_vbo.allocate(quadVertices, sizeof(quadVertices)); + + setupShaders(); + uploadTexture(); +} + +void DDSWidget::setupShaders() +{ + m_shader = new QOpenGLShaderProgram(this); + m_shader->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc); + + if (m_dds.isCubemap()) { + m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, cubemapFragSrc); + } else { + m_shader->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc); + } + + m_shader->link(); +} + +void DDSWidget::uploadTexture() +{ + if (m_dds.faceCount() == 0 || m_dds.mipCount() == 0) + return; + + const auto& fmt = m_dds.glFormat(); + + if (m_dds.isCubemap()) { + m_texture = new QOpenGLTexture(QOpenGLTexture::TargetCubeMap); + } else { + m_texture = new QOpenGLTexture(QOpenGLTexture::Target2D); + } + + m_texture->setAutoMipMapGenerationEnabled(false); + m_texture->setMipLevels(m_dds.mipCount()); + + if (m_dds.isCubemap()) { + m_texture->setSize(m_dds.width(), m_dds.height()); + // Set format for allocation + if (fmt.compressed) { + m_texture->setFormat( + static_cast<QOpenGLTexture::TextureFormat>(fmt.internalFormat)); + } else { + m_texture->setFormat( + static_cast<QOpenGLTexture::TextureFormat>(fmt.internalFormat)); + } + m_texture->allocateStorage(); + + static const QOpenGLTexture::CubeMapFace cubeMapFaces[] = { + QOpenGLTexture::CubeMapPositiveX, QOpenGLTexture::CubeMapNegativeX, + QOpenGLTexture::CubeMapPositiveY, QOpenGLTexture::CubeMapNegativeY, + QOpenGLTexture::CubeMapPositiveZ, QOpenGLTexture::CubeMapNegativeZ, + }; + + int numFaces = std::min(m_dds.faceCount(), 6); + for (int f = 0; f < numFaces; ++f) { + const auto& face = m_dds.face(f); + for (int m = 0; m < face.mips.size(); ++m) { + const auto& mip = face.mips[m]; + QByteArray pixelData = mip.data; + if (fmt.converter) { + pixelData = fmt.converter(mip.data, mip.width, mip.height); + } + if (fmt.compressed) { + m_texture->setCompressedData( + m, 0, cubeMapFaces[f], + pixelData.size(), + pixelData.constData()); + } else { + m_texture->setData( + m, 0, cubeMapFaces[f], + static_cast<QOpenGLTexture::PixelFormat>(fmt.format), + static_cast<QOpenGLTexture::PixelType>(fmt.type), + pixelData.constData()); + } + } + } + } else { + m_texture->setSize(m_dds.width(), m_dds.height()); + if (fmt.compressed) { + m_texture->setFormat( + static_cast<QOpenGLTexture::TextureFormat>(fmt.internalFormat)); + } else { + m_texture->setFormat( + static_cast<QOpenGLTexture::TextureFormat>(fmt.internalFormat)); + } + m_texture->allocateStorage(); + + const auto& face = m_dds.face(0); + for (int m = 0; m < face.mips.size(); ++m) { + const auto& mip = face.mips[m]; + QByteArray pixelData = mip.data; + if (fmt.converter) { + pixelData = fmt.converter(mip.data, mip.width, mip.height); + } + if (fmt.compressed) { + m_texture->setCompressedData( + m, 0, + pixelData.size(), + pixelData.constData()); + } else { + m_texture->setData( + m, 0, + static_cast<QOpenGLTexture::PixelFormat>(fmt.format), + static_cast<QOpenGLTexture::PixelType>(fmt.type), + pixelData.constData()); + } + } + } + + m_texture->setWrapMode(QOpenGLTexture::ClampToEdge); + if (fmt.sampler != SamplerType::Float) { + m_texture->setMinMagFilters(QOpenGLTexture::Nearest, + QOpenGLTexture::Nearest); + } else { + m_texture->setMinMagFilters(QOpenGLTexture::LinearMipMapLinear, + QOpenGLTexture::Linear); + } +} + +void DDSWidget::resizeGL(int w, int h) +{ + glViewport(0, 0, w, h); +} + +void DDSWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT); + + if (!m_texture || !m_shader) + return; + + m_shader->bind(); + m_texture->bind(); + + float widgetAspect = width() > 0 && height() > 0 + ? static_cast<float>(width()) / height() + : 1.0f; + m_shader->setUniformValue("uAspect", m_aspectRatio); + m_shader->setUniformValue("uWidgetAspect", widgetAspect); + m_shader->setUniformValue("uTexture", 0); + + m_vbo.bind(); + m_shader->enableAttributeArray(0); + m_shader->enableAttributeArray(1); + m_shader->setAttributeBuffer(0, GL_FLOAT, 0, 2, 4 * sizeof(float)); + m_shader->setAttributeBuffer(1, GL_FLOAT, 2 * sizeof(float), 2, + 4 * sizeof(float)); + + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + m_shader->disableAttributeArray(0); + m_shader->disableAttributeArray(1); + m_vbo.release(); + m_texture->release(); + m_shader->release(); +} diff --git a/libs/preview_dds_native/src/ddswidget.h b/libs/preview_dds_native/src/ddswidget.h new file mode 100644 index 0000000..968e253 --- /dev/null +++ b/libs/preview_dds_native/src/ddswidget.h @@ -0,0 +1,36 @@ +#ifndef DDSWIDGET_H +#define DDSWIDGET_H + +#include "ddsfile.h" + +#include <QOpenGLBuffer> +#include <QOpenGLFunctions> +#include <QOpenGLShaderProgram> +#include <QOpenGLTexture> +#include <QOpenGLWidget> + +class DDSWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + explicit DDSWidget(const DDSFile& dds, QWidget* parent = nullptr); + ~DDSWidget() override; + +protected: + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + +private: + void uploadTexture(); + void setupShaders(); + + const DDSFile& m_dds; + QOpenGLTexture* m_texture = nullptr; + QOpenGLShaderProgram* m_shader = nullptr; + QOpenGLBuffer m_vbo; + float m_aspectRatio = 1.0f; +}; + +#endif // DDSWIDGET_H |
