mirror of
https://github.com/ModOrganizer2/modorganizer-preview_dds.git
synced 2026-07-27 14:06:05 -07:00
Initial implementation which should have been added to version control ages ago
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
# Version chosen arbitrarily
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
|
||||
|
||||
PROJECT(preview_dds LANGUAGES NONE)
|
||||
|
||||
# Value passed from modorganizer-umbrella
|
||||
SET(DEPENDENCIES_DIR CACHE PATH "")
|
||||
|
||||
LIST(APPEND CMAKE_PREFIX_PATH ${QT_ROOT}/lib/cmake)
|
||||
|
||||
ADD_SUBDIRECTORY(src)
|
||||
@@ -0,0 +1,17 @@
|
||||
# Version chosen arbitrarily
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
|
||||
|
||||
FIND_PACKAGE(Qt5LinguistTools)
|
||||
INCLUDE(PyQt5TranslationMacros.cmake)
|
||||
PYQT5_CREATE_TRANSLATION(preview_dds_translations_qm ${CMAKE_SOURCE_DIR}/src ${CMAKE_SOURCE_DIR}/src/DDS ${CMAKE_SOURCE_DIR}/src/preview_dds_en.ts)
|
||||
|
||||
add_custom_target(translations ALL DEPENDS ${preview_dds_translations_qm})
|
||||
|
||||
add_subdirectory(DDS)
|
||||
|
||||
###############
|
||||
## Installation
|
||||
|
||||
INSTALL(FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DDSPreview.py
|
||||
DESTINATION bin/plugins)
|
||||
@@ -0,0 +1,14 @@
|
||||
# Version chosen arbitrarily
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.0)
|
||||
|
||||
# We don't need to add translations as the parent directory's CMakeLists has included this folder.
|
||||
|
||||
###############
|
||||
## Installation
|
||||
|
||||
INSTALL(FILES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DDSDefinitions.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/DDSFile.py
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/glstuff.py
|
||||
DESTINATION bin/plugins/data/DDS)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,193 @@
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, qDebug
|
||||
from PyQt5.QtGui import QOpenGLTexture
|
||||
|
||||
from . import DDSDefinitions
|
||||
|
||||
class DDSReadException(Exception):
|
||||
"""Thrown if there was an error reading a DDS file"""
|
||||
pass
|
||||
|
||||
ddsCubemapFaces = { DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP_POSITIVEX: QOpenGLTexture.CubeMapPositiveX,
|
||||
DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP_NEGATIVEX: QOpenGLTexture.CubeMapNegativeX,
|
||||
DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP_POSITIVEY: QOpenGLTexture.CubeMapPositiveY,
|
||||
DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP_NEGATIVEY: QOpenGLTexture.CubeMapNegativeY,
|
||||
DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP_POSITIVEZ: QOpenGLTexture.CubeMapPositiveZ,
|
||||
DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP_NEGATIVEZ: QOpenGLTexture.CubeMapNegativeZ }
|
||||
|
||||
class DDSFile:
|
||||
def __init__(self, fileName):
|
||||
self.fileName = fileName
|
||||
self.header = DDSDefinitions.DDS_HEADER()
|
||||
self.dxt10Header = None
|
||||
self.glFormat = None
|
||||
self.data = None
|
||||
self.isCubemap = None
|
||||
|
||||
def load(self):
|
||||
with Path(self.fileName).open('rb') as file:
|
||||
qDebug("Opened")
|
||||
magicNumber = file.read(4)
|
||||
if magicNumber != DDSDefinitions.DDS_MAGIC_NUMBER:
|
||||
qDebug(self.__tr("Magic number mismatch."))
|
||||
raise DDSReadException()
|
||||
|
||||
self.header.fromStream(file)
|
||||
qDebug(str(self.header))
|
||||
|
||||
if self.header.ddspf.dwFlags & DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_FOURCC:
|
||||
fourCC = self.header.ddspf.dwFourCC
|
||||
if fourCC == b"DX10":
|
||||
self.dxt10Header = DDSDefinitions.DDS_HEADER_DXT10()
|
||||
self.dxt10Header.fromStream(file)
|
||||
qDebug(str(self.dxt10Header))
|
||||
else:
|
||||
fourCC = None
|
||||
|
||||
self.glFormat = DDSDefinitions.getGLFormat(self.header.ddspf, self.dxt10Header)
|
||||
self.data = []
|
||||
# Do this once per layer/mip level whatever, (times one per scanline if uncompressed). Also, potentially recompute this based on the format and size in case writers lie.
|
||||
#self.data.append(file.read(self.header.dwPitchOrLinearSize))
|
||||
|
||||
layerCount = 1
|
||||
if self.header.dwCaps2 & DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP:
|
||||
self.isCubemap = True
|
||||
layerCount = 0
|
||||
for face in ddsCubemapFaces:
|
||||
if self.header.dwCaps2 & face:
|
||||
layerCount += 1
|
||||
else:
|
||||
self.isCubemap = False
|
||||
|
||||
for layer in range(layerCount):
|
||||
nextWidth = self.header.dwWidth
|
||||
nextHeight = self.header.dwHeight
|
||||
mipCount = self.mipLevels()
|
||||
for level in range(mipCount):
|
||||
if self.header.ddspf.dwFlags & (DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_ALPHA | DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_RGB | DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_YUV | DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_LUMINANCE):
|
||||
size = nextWidth * nextHeight * ((self.header.ddspf.dwRGBBitCount + 7) // 8)
|
||||
elif fourCC:
|
||||
if self.dxt10Header:
|
||||
dxgiFormat = self.dxt10Header.dxgiFormat
|
||||
else:
|
||||
dxgiFormat = DDSDefinitions.fourCCToDXGI(fourCC)
|
||||
size = DDSDefinitions.sizeFromFormat(dxgiFormat, nextWidth, nextHeight)
|
||||
self.data.append(file.read(size))
|
||||
nextWidth = max(nextWidth // 2, 1)
|
||||
nextHeight = max(nextHeight // 2, 1)
|
||||
|
||||
def getDescription(self):
|
||||
format = ""
|
||||
# DX10 header says the format enum
|
||||
if self.dxt10Header != None:
|
||||
format = self.dxt10Header.dxgiFormat.name.replace("DXGI_FORMAT_", "")
|
||||
# Pixel Format says the FourCC
|
||||
elif self.header.ddspf.dwFlags & DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_FOURCC:
|
||||
fourCC = self.header.ddspf.dwFourCC
|
||||
format = self.__tr("{0} (equivalent to {1})").format(fourCC.decode('ascii'), DDSDefinitions.fourCCToDXGI(fourCC).name.replace("DXGI_FORMAT_", ""))
|
||||
# We've got bitmasks for the colour channels
|
||||
else:
|
||||
# This could be prettier if there was logic to detect that certain common bitmasks represented things more easily represented, like RGBA8
|
||||
if self.header.ddspf.dwFlags & (DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_RGB | DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_YUV):
|
||||
format += self.__tr("Red bitmask {0}, Green bitmask {1}, Blue bitmask {2}").format(self.header.ddspf.dwRBitMask.hex().upper(), self.header.ddspf.dwGBitMask.hex().upper(), self.header.ddspf.dwBBitMask.hex().upper())
|
||||
if self.header.ddspf.dwFlags & DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_LUMINANCE:
|
||||
if format != "":
|
||||
format += ", "
|
||||
format += self.__tr("Luminance bitmask {0}").format(self.header.ddspf.dwRBitMask.hex().upper())
|
||||
if self.header.ddspf.dwFlags & (DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_ALPHA | DDSDefinitions.DDS_PIXELFORMAT.Flags.DDPF_ALPHAPIXELS):
|
||||
if format != "":
|
||||
format += ", "
|
||||
format += self.__tr("Alpha bitmask {0}").format(self.header.ddspf.dwABitMask.hex().upper())
|
||||
|
||||
size = self.__tr("{0}×{1}").format(self.header.dwWidth, self.header.dwHeight)
|
||||
|
||||
dimensions = self.__tr("Cubemap") if self.isCubemap else self.__tr("2D")
|
||||
|
||||
mipmaps = self.__tr("Mipmapped") if self.mipLevels() != 1 else self.__tr("No mipmaps")
|
||||
|
||||
return self.__tr("{0}, {1} {2}, {3}").format(format, size, dimensions, mipmaps)
|
||||
|
||||
def mipLevels(self):
|
||||
if self.header.dwFlags & DDSDefinitions.DDS_HEADER.Flags.DDSD_MIPMAPCOUNT:
|
||||
return self.header.dwMipMapCount
|
||||
else:
|
||||
return 1
|
||||
|
||||
def asQOpenGLTexture(self, gl, context):
|
||||
if not self.data:
|
||||
return
|
||||
|
||||
if self.glFormat.requirements:
|
||||
minVersion, extensions = self.glFormat.requirements
|
||||
glVersion = (gl.glGetIntegerv(gl.GL_MAJOR_VERSION), gl.glGetIntegerv(gl.GL_MINOR_VERSION))
|
||||
if glVersion < minVersion or minVersion < (1, 0):
|
||||
compatible = False
|
||||
for extension in extensions:
|
||||
if context.hasExtension(extension):
|
||||
compatible = True
|
||||
break
|
||||
if not compatible:
|
||||
qDebug(self.__tr("OpenGL driver incompatible with texture format."))
|
||||
return None
|
||||
|
||||
if self.header.dwCaps2 & DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP:
|
||||
texture = QOpenGLTexture(QOpenGLTexture.TargetCubeMap)
|
||||
if self.header.dwWidth != self.header.dwHeight:
|
||||
qDebug(self.__tr("Cubemap faces must be square"))
|
||||
return None
|
||||
else:
|
||||
# Assume GL_TEXTURE_2D for now
|
||||
texture = QOpenGLTexture(QOpenGLTexture.Target2D)
|
||||
# Assume single layer for now
|
||||
# self.texture.setLayers(1)
|
||||
mipCount = self.mipLevels()
|
||||
texture.setAutoMipMapGenerationEnabled(False)
|
||||
texture.setMipLevels(mipCount)
|
||||
texture.setMipLevelRange(0, mipCount - 1)
|
||||
texture.setSize(self.header.dwWidth, self.header.dwHeight)
|
||||
qDebug(str(self.glFormat.internalFormat))
|
||||
texture.setFormat(self.glFormat.internalFormat)
|
||||
texture.allocateStorage()
|
||||
|
||||
if not self.glFormat.compressed:
|
||||
qDebug(str(self.glFormat.format))
|
||||
qDebug(str(self.glFormat.type))
|
||||
|
||||
if self.header.dwCaps2 & DDSDefinitions.DDS_HEADER.Caps2.DDSCAPS2_CUBEMAP:
|
||||
# Lisa hasn't whipped David Wang into shape yet. At least there are fewer bugs than under Raja.
|
||||
# The specific bug has been reported and AMD "will try to reproduce it soon"
|
||||
noDSA = "Radeon" in gl.glGetString(gl.GL_RENDERER) and self.glFormat.compressed
|
||||
if noDSA:
|
||||
texture.bind()
|
||||
faceIndex = 0
|
||||
for face in ddsCubemapFaces:
|
||||
if self.header.dwCaps2 & face:
|
||||
for i in range(mipCount):
|
||||
if self.glFormat.compressed:
|
||||
if not noDSA:
|
||||
texture.setCompressedData(i, 0, ddsCubemapFaces[face], len(self.data[faceIndex * mipCount + i]), self.data[faceIndex * mipCount + i])
|
||||
else:
|
||||
gl.glCompressedTexSubImage2D(ddsCubemapFaces[face], i, 0, 0, max(self.header.dwWidth // 2 ** i, 1), max(self.header.dwHeight // 2 ** i, 1), self.glFormat.internalFormat, len(self.data[faceIndex * mipCount + i]), self.data[faceIndex * mipCount + i])
|
||||
else:
|
||||
texture.setData(i, 0, ddsCubemapFaces[face], self.glFormat.format, self.glFormat.type, self.glFormat.converter(self.data[faceIndex * mipCount + i]))
|
||||
faceIndex += 1
|
||||
if noDSA:
|
||||
texture.release()
|
||||
else:
|
||||
for i in range(mipCount):
|
||||
if self.glFormat.compressed:
|
||||
texture.setCompressedData(i, 0, len(self.data[i]), self.data[i])
|
||||
else:
|
||||
texture.setData(i, 0, self.glFormat.format, self.glFormat.type, self.glFormat.converter(self.data[i]))
|
||||
|
||||
texture.setWrapMode(QOpenGLTexture.ClampToEdge)
|
||||
|
||||
if self.glFormat.samplerType != "F":
|
||||
# integer textures can't be filtered
|
||||
texture.setMinMagFilters(QOpenGLTexture.NearestMipMapNearest, QOpenGLTexture.Nearest)
|
||||
|
||||
return texture
|
||||
|
||||
def __tr(self, str):
|
||||
return QCoreApplication.translate("DDSFile", str)
|
||||
@@ -0,0 +1,201 @@
|
||||
from enum import IntEnum
|
||||
|
||||
class GL_IMAGE_FORMAT(IntEnum):
|
||||
GL_BYTE = 0x1400
|
||||
GL_UNSIGNED_BYTE = 0x1401
|
||||
GL_SHORT = 0x1402
|
||||
GL_UNSIGNED_SHORT = 0x1403
|
||||
GL_INT = 0x1404
|
||||
GL_UNSIGNED_INT = 0x1405
|
||||
GL_FLOAT = 0x1406
|
||||
GL_HALF_FLOAT = 0x140B
|
||||
GL_COLOR_INDEX = 0x1900
|
||||
GL_STENCIL_INDEX = 0x1901
|
||||
GL_DEPTH_COMPONENT = 0x1902
|
||||
GL_RED = 0x1903
|
||||
GL_GREEN = 0x1904
|
||||
GL_BLUE = 0x1905
|
||||
GL_ALPHA = 0x1906
|
||||
GL_RGB = 0x1907
|
||||
GL_RGBA = 0x1908
|
||||
GL_LUMINANCE = 0x1909
|
||||
GL_LUMINANCE_ALPHA = 0x190A
|
||||
GL_BITMAP = 0x1A00
|
||||
GL_R3_G3_B2 = 0x2A10
|
||||
GL_UNSIGNED_BYTE_3_3_2 = 0x8032
|
||||
GL_UNSIGNED_SHORT_4_4_4_4 = 0x8033
|
||||
GL_UNSIGNED_SHORT_5_5_5_1 = 0x8034
|
||||
GL_UNSIGNED_INT_8_8_8_8 = 0x8035
|
||||
GL_UNSIGNED_INT_10_10_10_2 = 0x8036
|
||||
GL_ALPHA4 = 0x803B
|
||||
GL_ALPHA8 = 0x803C
|
||||
GL_ALPHA12 = 0x803D
|
||||
GL_ALPHA16 = 0x803E
|
||||
GL_LUMINANCE4 = 0x803F
|
||||
GL_LUMINANCE8 = 0x8040
|
||||
GL_LUMINANCE12 = 0x8041
|
||||
GL_LUMINANCE16 = 0x8042
|
||||
GL_LUMINANCE4_ALPHA4 = 0x8043
|
||||
GL_LUMINANCE6_ALPHA2 = 0x8044
|
||||
GL_LUMINANCE8_ALPHA8 = 0x8045
|
||||
GL_LUMINANCE12_ALPHA4 = 0x8046
|
||||
GL_LUMINANCE12_ALPHA12 = 0x8047
|
||||
GL_LUMINANCE16_ALPHA16 = 0x8048
|
||||
GL_INTENSITY = 0x8049
|
||||
GL_INTENSITY4 = 0x804A
|
||||
GL_INTENSITY8 = 0x804B
|
||||
GL_INTENSITY12 = 0x804C
|
||||
GL_INTENSITY16 = 0x804D
|
||||
GL_RGB4 = 0x804F
|
||||
GL_RGB5 = 0x8050
|
||||
GL_RGB8 = 0x8051
|
||||
GL_RGB10 = 0x8052
|
||||
GL_RGB12 = 0x8053
|
||||
GL_RGB16 = 0x8054
|
||||
GL_RGBA2 = 0x8055
|
||||
GL_RGBA4 = 0x8056
|
||||
GL_RGB5_A1 = 0x8057
|
||||
GL_RGBA8 = 0x8058
|
||||
GL_RGB10_A2 = 0x8059
|
||||
GL_RGBA12 = 0x805A
|
||||
GL_RGBA16 = 0x805B
|
||||
GL_BGR = 0x80E0
|
||||
GL_BGRA = 0x80E1
|
||||
GL_DEPTH_COMPONENT16 = 0x81A5
|
||||
GL_DEPTH_COMPONENT24 = 0x81A6
|
||||
GL_DEPTH_COMPONENT32 = 0x81A7
|
||||
GL_COMPRESSED_RED = 0x8225
|
||||
GL_COMPRESSED_RG = 0x8226
|
||||
GL_RG = 0x8227
|
||||
GL_RG_INTEGER = 0x8228
|
||||
GL_R8 = 0x8229
|
||||
GL_R16 = 0x822A
|
||||
GL_RG8 = 0x822B
|
||||
GL_RG16 = 0x822C
|
||||
GL_R16F = 0x822D
|
||||
GL_R32F = 0x822E
|
||||
GL_RG16F = 0x822F
|
||||
GL_RG32F = 0x8230
|
||||
GL_R8I = 0x8231
|
||||
GL_R8UI = 0x8232
|
||||
GL_R16I = 0x8233
|
||||
GL_R16UI = 0x8234
|
||||
GL_R32I = 0x8235
|
||||
GL_R32UI = 0x8236
|
||||
GL_RG8I = 0x8237
|
||||
GL_RG8UI = 0x8238
|
||||
GL_RG16I = 0x8239
|
||||
GL_RG16UI = 0x823A
|
||||
GL_RG32I = 0x823B
|
||||
GL_RG32UI = 0x823C
|
||||
GL_UNSIGNED_BYTE_2_3_3_REV = 0x8362
|
||||
GL_UNSIGNED_SHORT_5_6_5 = 0x8363
|
||||
GL_UNSIGNED_SHORT_5_6_5_REV = 0x8364
|
||||
GL_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365
|
||||
GL_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV = 0x8367
|
||||
GL_UNSIGNED_INT_2_10_10_10_REV = 0x8368
|
||||
GL_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0
|
||||
GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1
|
||||
GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2
|
||||
GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3
|
||||
GL_COMPRESSED_ALPHA = 0x84E9
|
||||
GL_COMPRESSED_LUMINANCE = 0x84EA
|
||||
GL_COMPRESSED_LUMINANCE_ALPHA = 0x84EB
|
||||
GL_COMPRESSED_INTENSITY = 0x84EC
|
||||
GL_COMPRESSED_RGB = 0x84ED
|
||||
GL_COMPRESSED_RGBA = 0x84EE
|
||||
GL_DEPTH_STENCIL = 0x84F9
|
||||
GL_RGBA32F = 0x8814
|
||||
GL_RGB32F = 0x8815
|
||||
GL_RGBA16F = 0x881A
|
||||
GL_RGB16F = 0x881B
|
||||
GL_R11F_G11F_B10F = 0x8C3A
|
||||
GL_UNSIGNED_INT_10F_11F_11F_REV_EXT = 0x8C3B # EXT_packed_float
|
||||
GL_RGB9_E5 = 0x8C3D
|
||||
GL_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E # EXT_texture_shared_exponent
|
||||
GL_SRGB = 0x8C40
|
||||
GL_SRGB8 = 0x8C41
|
||||
GL_SRGB_ALPHA = 0x8C42
|
||||
GL_SRGB8_ALPHA8 = 0x8C43
|
||||
GL_SLUMINANCE_ALPHA = 0x8C44
|
||||
GL_SLUMINANCE8_ALPHA8 = 0x8C45
|
||||
GL_SLUMINANCE = 0x8C46
|
||||
GL_SLUMINANCE8 = 0x8C47
|
||||
GL_COMPRESSED_SRGB = 0x8C48
|
||||
GL_COMPRESSED_SRGB_ALPHA = 0x8C49
|
||||
GL_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C
|
||||
GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D
|
||||
GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E
|
||||
GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F
|
||||
GL_RGBA32UI = 0x8D70
|
||||
GL_RGB32UI = 0x8D71
|
||||
GL_RGBA16UI = 0x8D76
|
||||
GL_RGB16UI = 0x8D77
|
||||
GL_RGBA8UI = 0x8D7C
|
||||
GL_RGB8UI = 0x8D7D
|
||||
GL_RGBA32I = 0x8D82
|
||||
GL_RGB32I = 0x8D83
|
||||
GL_RGBA16I = 0x8D88
|
||||
GL_RGB16I = 0x8D89
|
||||
GL_RGBA8I = 0x8D8E
|
||||
GL_RGB8I = 0x8D8F
|
||||
GL_RED_INTEGER = 0x8D94
|
||||
GL_RGB_INTEGER = 0x8D98
|
||||
GL_RGBA_INTEGER = 0x8D99
|
||||
GL_BGR_INTEGER = 0x8D9A
|
||||
GL_BGRA_INTEGER = 0x8D9B
|
||||
GL_COMPRESSED_RED_RGTC1 = 0x8DBB
|
||||
GL_COMPRESSED_SIGNED_RED_RGTC1 = 0x8DBC
|
||||
GL_COMPRESSED_RG_RGTC2 = 0x8DBD
|
||||
GL_COMPRESSED_SIGNED_RG_RGTC2 = 0x8DBE
|
||||
GL_COMPRESSED_RGBA_BPTC_UNORM = 0x8E8C
|
||||
GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM = 0x8E8D
|
||||
GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E
|
||||
GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F
|
||||
GL_R8_SNORM = 0x8F94
|
||||
GL_RG8_SNORM = 0x8F95
|
||||
GL_RGB8_SNORM = 0x8F96
|
||||
GL_RGBA8_SNORM = 0x8F97
|
||||
GL_R16_SNORM = 0x8F98
|
||||
GL_RG16_SNORM = 0x8F99
|
||||
GL_RGB16_SNORM = 0x8F9A
|
||||
GL_RGBA16_SNORM = 0x8F9B
|
||||
GL_RGB10_A2UI = 0x906F
|
||||
GL_COMPRESSED_R11_EAC = 0x9270
|
||||
GL_COMPRESSED_SIGNED_R11_EAC = 0x9271
|
||||
GL_COMPRESSED_RG11_EAC = 0x9272
|
||||
GL_COMPRESSED_SIGNED_RG11_EAC = 0x9273
|
||||
GL_COMPRESSED_RGB8_ETC2 = 0x9274
|
||||
GL_COMPRESSED_SRGB8_ETC2 = 0x9275
|
||||
GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276
|
||||
GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277
|
||||
GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278
|
||||
GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279
|
||||
|
||||
class GLTextureFormat:
|
||||
def __init__(self, requirements, internalFormat, compressed):
|
||||
self.requirements = requirements
|
||||
self.internalFormat = internalFormat
|
||||
self.compressed = compressed
|
||||
|
||||
if internalFormat.name.endswith("UI"):
|
||||
self.samplerType = "UI"
|
||||
elif internalFormat.name.endswith("I"):
|
||||
self.samplerType = "I"
|
||||
else:
|
||||
self.samplerType = "F"
|
||||
|
||||
class CompressedGLTextureFormat(GLTextureFormat):
|
||||
def __init__(self, requirements, internalFormat):
|
||||
super().__init__(requirements, internalFormat, True)
|
||||
|
||||
class UncompressedGLTextureFormat(GLTextureFormat):
|
||||
def __init__(self, requirements, internalFormat, format, type, converter = None):
|
||||
super().__init__(requirements, internalFormat, False)
|
||||
self.format = format
|
||||
self.type = type
|
||||
if converter:
|
||||
self.converter = converter
|
||||
else:
|
||||
self.converter = lambda x: x
|
||||
@@ -0,0 +1,396 @@
|
||||
import struct
|
||||
import sys
|
||||
import threading
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, qDebug, Qt
|
||||
from PyQt5.QtGui import QColor, QOpenGLBuffer, QOpenGLContext, QOpenGLDebugLogger, QOpenGLShader, QOpenGLShaderProgram, QOpenGLTexture, QOpenGLVersionProfile, QOpenGLVertexArrayObject, QSurfaceFormat
|
||||
from PyQt5.QtWidgets import QCheckBox, QDialog, QGridLayout, QLabel, QOpenGLWidget, QPushButton, QWidget
|
||||
|
||||
from DDS.DDSFile import DDSFile
|
||||
|
||||
if "mobase" not in sys.modules:
|
||||
import mock_mobase as mobase
|
||||
|
||||
vertexShader2D = """
|
||||
#version 150
|
||||
|
||||
uniform float aspectRatioRatio;
|
||||
|
||||
in vec4 position;
|
||||
in vec2 texCoordIn;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
texCoord = texCoordIn;
|
||||
gl_Position = position;
|
||||
if (aspectRatioRatio >= 1.0)
|
||||
gl_Position.y /= aspectRatioRatio;
|
||||
else
|
||||
gl_Position.x *= aspectRatioRatio;
|
||||
}
|
||||
"""
|
||||
|
||||
vertexShaderCube = """
|
||||
#version 150
|
||||
|
||||
uniform float aspectRatioRatio;
|
||||
|
||||
in vec4 position;
|
||||
in vec2 texCoordIn;
|
||||
|
||||
out vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
texCoord = texCoordIn;
|
||||
gl_Position = position;
|
||||
}
|
||||
"""
|
||||
|
||||
fragmentShaderFloat = """
|
||||
#version 150
|
||||
|
||||
uniform sampler2D aTexture;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_FragData[0] = texture(aTexture, texCoord);
|
||||
}
|
||||
"""
|
||||
|
||||
fragmentShaderUInt = """
|
||||
#version 150
|
||||
|
||||
uniform usampler2D aTexture;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
// autofilled alpha is 1, so if we have a scaling factor, we need separate ones for luminance and alpha
|
||||
gl_FragData[0] = texture(aTexture, texCoord);
|
||||
}
|
||||
"""
|
||||
|
||||
fragmentShaderSInt = """
|
||||
#version 150
|
||||
|
||||
uniform isampler2D aTexture;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
void main()
|
||||
{
|
||||
// autofilled alpha is 1, so if we have a scaling factor and offset, we need separate ones for luminance and alpha
|
||||
gl_FragData[0] = texture(aTexture, texCoord);
|
||||
}
|
||||
"""
|
||||
|
||||
fragmentShaderCube = """
|
||||
#version 150
|
||||
|
||||
uniform samplerCube aTexture;
|
||||
|
||||
in vec2 texCoord;
|
||||
|
||||
const float PI = 3.1415926535897932384626433832795;
|
||||
|
||||
void main()
|
||||
{
|
||||
float theta = -2.0 * PI * texCoord.x;
|
||||
float phi = PI * texCoord.y;
|
||||
gl_FragData[0] = texture(aTexture, vec3(sin(theta) * sin(phi), cos(theta) * sin(phi), cos(phi)));
|
||||
}
|
||||
"""
|
||||
|
||||
transparencyVS = """
|
||||
#version 150
|
||||
|
||||
in vec4 position;
|
||||
|
||||
void main()
|
||||
{
|
||||
gl_Position = position;
|
||||
}
|
||||
"""
|
||||
|
||||
transparencyFS = """
|
||||
#version 150
|
||||
|
||||
uniform vec4 backgroundColour;
|
||||
|
||||
void main()
|
||||
{
|
||||
float x = gl_FragCoord.x;
|
||||
float y = gl_FragCoord.y;
|
||||
x = mod(x, 16.0);
|
||||
y = mod(y, 16.0);
|
||||
gl_FragData[0] = x < 8.0 ^^ y < 8.0 ? vec4(vec3(191.0/255.0), 1.0) : vec4(1.0);
|
||||
gl_FragData[0].rgb = backgroundColour.rgb * backgroundColour.a + gl_FragData[0].rgb * (1.0 - backgroundColour.a);
|
||||
}
|
||||
"""
|
||||
|
||||
vertices = [
|
||||
# vertex coordinates texture coordinates
|
||||
-1.0, -1.0, 0.5, 1.0, 0.0, 1.0,
|
||||
-1.0, 1.0, 0.5, 1.0, 0.0, 0.0,
|
||||
1.0, 1.0, 0.5, 1.0, 1.0, 0.0,
|
||||
|
||||
-1.0, -1.0, 0.5, 1.0, 0.0, 1.0,
|
||||
1.0, 1.0, 0.5, 1.0, 1.0, 0.0,
|
||||
1.0, -1.0, 0.5, 1.0, 1.0, 1.0,
|
||||
]
|
||||
|
||||
|
||||
|
||||
glVersionProfile = QOpenGLVersionProfile()
|
||||
glVersionProfile.setVersion(2, 1)
|
||||
|
||||
class DDSWidget(QOpenGLWidget):
|
||||
def __init__(self, ddsFile, debugContext = False, parent = None, f = Qt.WindowFlags()):
|
||||
super(DDSWidget, self).__init__(parent, f)
|
||||
|
||||
self.ddsFile = ddsFile
|
||||
|
||||
self.clean = True
|
||||
|
||||
self.logger = None
|
||||
|
||||
self.program = None
|
||||
self.transparecyProgram = None
|
||||
self.texture = None
|
||||
self.vbo = None
|
||||
self.vao = None
|
||||
|
||||
self.backgroundColour = None
|
||||
|
||||
if debugContext:
|
||||
format = QSurfaceFormat()
|
||||
format.setOption(QSurfaceFormat.DebugContext)
|
||||
self.setFormat(format)
|
||||
self.logger = QOpenGLDebugLogger(self)
|
||||
|
||||
qDebug("__init__()")
|
||||
|
||||
def __del__(self):
|
||||
qDebug("__del__()")
|
||||
self.cleanup()
|
||||
|
||||
def __dtor__(self):
|
||||
qDebug("__dtor__()")
|
||||
self.cleanup()
|
||||
|
||||
def initializeGL(self):
|
||||
qDebug("initializeGL()")
|
||||
if self.logger:
|
||||
self.logger.initialize()
|
||||
self.logger.messageLogged.connect(lambda message: qDebug(self.__tr("OpenGL debug message: {0}").fomat(message.message())))
|
||||
self.logger.startLogging()
|
||||
|
||||
gl = QOpenGLContext.currentContext().versionFunctions(glVersionProfile)
|
||||
QOpenGLContext.currentContext().aboutToBeDestroyed.connect(self.cleanup)
|
||||
|
||||
self.clean = False
|
||||
|
||||
fragmentShader = None
|
||||
vertexShader = vertexShader2D
|
||||
if self.ddsFile.isCubemap:
|
||||
fragmentShader = fragmentShaderCube
|
||||
vertexShader = vertexShaderCube
|
||||
if QOpenGLContext.currentContext().hasExtension(b"GL_ARB_seamless_cube_map"):
|
||||
GL_TEXTURE_CUBE_MAP_SEAMLESS = 0x884F
|
||||
gl.glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS)
|
||||
elif self.ddsFile.glFormat.samplerType == "F":
|
||||
fragmentShader = fragmentShaderFloat
|
||||
elif self.ddsFile.glFormat.samplerType == "UI":
|
||||
fragmentShader = fragmentShaderUInt
|
||||
else:
|
||||
fragmentShader = fragmentShaderSInt
|
||||
|
||||
self.program = QOpenGLShaderProgram(self)
|
||||
self.program.addShaderFromSourceCode(QOpenGLShader.Vertex, vertexShader)
|
||||
self.program.addShaderFromSourceCode(QOpenGLShader.Fragment, fragmentShader)
|
||||
self.program.bindAttributeLocation("position", 0)
|
||||
self.program.bindAttributeLocation("texCoordIn", 1)
|
||||
self.program.link()
|
||||
|
||||
self.transparecyProgram = QOpenGLShaderProgram(self)
|
||||
self.transparecyProgram.addShaderFromSourceCode(QOpenGLShader.Vertex, transparencyVS)
|
||||
self.transparecyProgram.addShaderFromSourceCode(QOpenGLShader.Fragment, transparencyFS)
|
||||
self.transparecyProgram.bindAttributeLocation("position", 0)
|
||||
self.transparecyProgram.link()
|
||||
|
||||
self.vao = QOpenGLVertexArrayObject(self)
|
||||
vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)
|
||||
|
||||
self.vbo = QOpenGLBuffer(QOpenGLBuffer.VertexBuffer)
|
||||
self.vbo.create()
|
||||
self.vbo.bind()
|
||||
|
||||
theBytes = struct.pack("%sf" % len(vertices), *vertices)
|
||||
self.vbo.allocate(theBytes, len(theBytes))
|
||||
|
||||
gl.glEnableVertexAttribArray(0)
|
||||
gl.glEnableVertexAttribArray(1)
|
||||
gl.glVertexAttribPointer(0, 4, gl.GL_FLOAT, False, 6 * 4, 0)
|
||||
gl.glVertexAttribPointer(1, 2, gl.GL_FLOAT, False, 6 * 4, 4 * 4)
|
||||
|
||||
self.texture = self.ddsFile.asQOpenGLTexture(gl, QOpenGLContext.currentContext())
|
||||
|
||||
def resizeGL(self, w, h):
|
||||
qDebug("resizeGL(" + str(w) + ", " + str(h) + ")")
|
||||
aspectRatioTex = self.texture.width() / self.texture.height() if self.texture else 1.0
|
||||
aspectRatioWidget = w / h
|
||||
ratioRatio = aspectRatioTex / aspectRatioWidget
|
||||
|
||||
self.program.bind()
|
||||
self.program.setUniformValue("aspectRatioRatio", ratioRatio)
|
||||
self.program.release()
|
||||
|
||||
def paintGL(self):
|
||||
qDebug("paintGL()")
|
||||
gl = QOpenGLContext.currentContext().versionFunctions(glVersionProfile)
|
||||
|
||||
vaoBinder = QOpenGLVertexArrayObject.Binder(self.vao)
|
||||
|
||||
# Draw checkerboard so transparency is obvious
|
||||
self.transparecyProgram.bind()
|
||||
|
||||
if self.backgroundColour and self.backgroundColour.isValid():
|
||||
self.transparecyProgram.setUniformValue("backgroundColour", self.backgroundColour)
|
||||
|
||||
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
|
||||
|
||||
self.transparecyProgram.release()
|
||||
|
||||
self.program.bind()
|
||||
|
||||
if self.texture:
|
||||
self.texture.bind()
|
||||
|
||||
gl.glEnable(gl.GL_BLEND)
|
||||
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
|
||||
|
||||
gl.glDrawArrays(gl.GL_TRIANGLES, 0, 6)
|
||||
|
||||
if self.texture:
|
||||
self.texture.release()
|
||||
self.program.release()
|
||||
|
||||
def cleanup(self):
|
||||
qDebug("cleanup()")
|
||||
if not self.clean:
|
||||
self.makeCurrent()
|
||||
|
||||
self.program = None
|
||||
self.transparecyProgram = None
|
||||
if self.texture:
|
||||
self.texture.destroy()
|
||||
self.texture = None
|
||||
self.vbo.destroy()
|
||||
self.vbo = None
|
||||
self.vao.destroy()
|
||||
self.vao = None
|
||||
|
||||
self.doneCurrent()
|
||||
self.clean = True
|
||||
|
||||
def setBackgroundColour(self, colour):
|
||||
self.backgroundColour = colour
|
||||
|
||||
def getBackgroundColour(self):
|
||||
return self.backgroundColour
|
||||
|
||||
def __tr(self, str):
|
||||
return QCoreApplication.translate("DDSWidget", str)
|
||||
|
||||
|
||||
class DDSPreview(mobase.IPluginPreview):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.__organizer = None
|
||||
|
||||
def init(self, organizer):
|
||||
self.__organizer = organizer
|
||||
return True
|
||||
|
||||
def name(self):
|
||||
return "DDS Preview Plugin"
|
||||
|
||||
def author(self):
|
||||
return "AnyOldName3"
|
||||
|
||||
def description(self):
|
||||
return self.__tr("Lets you preview DDS files by actually uploading them to the GPU.")
|
||||
|
||||
def version(self):
|
||||
return mobase.VersionInfo(0, 1, 0, mobase.ReleaseType.prealpha)
|
||||
|
||||
def isActive(self):
|
||||
return True
|
||||
|
||||
def settings(self):
|
||||
return [mobase.PluginSetting("log gl errors", self.__tr("If enabled, log OpenGL errors and debug messages. May decrease performance."), False),
|
||||
mobase.PluginSetting("background r", self.__tr("Red channel of background colour"), 0),
|
||||
mobase.PluginSetting("background g", self.__tr("Green channel of background colour"), 0),
|
||||
mobase.PluginSetting("background b", self.__tr("Blue channel of background colour"), 0),
|
||||
mobase.PluginSetting("background a", self.__tr("Alpha channel of background colour"), 0)]
|
||||
|
||||
def supportedExtensions(self):
|
||||
return ["dds"]
|
||||
|
||||
def genFilePreview(self, fileName, maxSize):
|
||||
qDebug(fileName)
|
||||
ddsFile = DDSFile(fileName)
|
||||
ddsFile.load()
|
||||
layout = QGridLayout()
|
||||
# Image grows before label and button
|
||||
layout.setRowStretch(0, 1)
|
||||
# Label grows before button
|
||||
layout.setColumnStretch(0, 1)
|
||||
layout.addWidget(self.__makeLabel(ddsFile), 1, 0, 1, 1)
|
||||
|
||||
ddsWidget = DDSWidget(ddsFile, self.__organizer.pluginSetting(self.name(), "log gl errors"))
|
||||
layout.addWidget(ddsWidget, 0, 0, 1, 2)
|
||||
|
||||
layout.addWidget(self.__makeColourButton(ddsWidget), 1, 1, 1, 1)
|
||||
|
||||
widget = QWidget()
|
||||
widget.setLayout(layout)
|
||||
return widget
|
||||
|
||||
def __tr(self, str):
|
||||
return QCoreApplication.translate("DDSPreview", str)
|
||||
|
||||
def __makeLabel(self, ddsFile):
|
||||
label = QLabel(ddsFile.getDescription())
|
||||
label.setWordWrap(True)
|
||||
label.setTextInteractionFlags(Qt.TextSelectableByMouse)
|
||||
return label
|
||||
|
||||
def __makeColourButton(self, ddsWidget):
|
||||
button = QPushButton(self.__tr("Pick background colour"))
|
||||
savedColour = QColor(self.__organizer.pluginSetting(self.name(), "background r"), self.__organizer.pluginSetting(self.name(), "background g"), self.__organizer.pluginSetting(self.name(), "background b"), self.__organizer.pluginSetting(self.name(), "background a"))
|
||||
ddsWidget.setBackgroundColour(savedColour)
|
||||
|
||||
def pickColour(unused):
|
||||
newColour = QColorDialog.getColor(ddsWidget.getBackgroundColour(), button, "Background colour", QColorDialog.ShowAlphaChannel)
|
||||
if newColour.isValid():
|
||||
ddsWidget.setBackgroundColour(newColour)
|
||||
print(str(type(self)))
|
||||
print(str(type(self.__organizer)))
|
||||
self.__organizer.setPluginSetting(self.name(), "background r", newColour.red())
|
||||
self.__organizer.setPluginSetting(self.name(), "background g", newColour.green())
|
||||
self.__organizer.setPluginSetting(self.name(), "background b", newColour.blue())
|
||||
self.__organizer.setPluginSetting(self.name(), "background a", newColour.alpha())
|
||||
|
||||
button.clicked.connect(pickColour)
|
||||
return button
|
||||
|
||||
def createPlugin():
|
||||
return DDSPreview()
|
||||
@@ -0,0 +1,88 @@
|
||||
# This is a modified version of Qt5LinguistToolsMacros.cmake which calls
|
||||
# pylupdate5 instead of lupdate, allowing Python strings to be extracted,
|
||||
# too. It still requires the Qt version of the file to be included, but
|
||||
# only this version of the function needs to be called. You also need to
|
||||
# have PYTHON_ROOT set to a directory where a working pylupdate5.bat can
|
||||
# be found. If you aren't using Windows, your platform's equivalent may
|
||||
# work, too.
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2005-2011 Kitware, Inc.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions
|
||||
# are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
#
|
||||
# * Redistributions in binary form must reproduce the above copyright
|
||||
# notice, this list of conditions and the following disclaimer in the
|
||||
# documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# * Neither the name of Kitware, Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived
|
||||
# from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#=============================================================================
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
function(PYQT5_CREATE_TRANSLATION _qm_files)
|
||||
set(options)
|
||||
set(oneValueArgs)
|
||||
set(multiValueArgs OPTIONS)
|
||||
|
||||
cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
|
||||
set(_lupdate_files ${_LUPDATE_UNPARSED_ARGUMENTS})
|
||||
set(_lupdate_options ${_LUPDATE_OPTIONS})
|
||||
|
||||
set(_my_sources)
|
||||
set(_my_tsfiles)
|
||||
foreach(_file ${_lupdate_files})
|
||||
get_filename_component(_ext ${_file} EXT)
|
||||
get_filename_component(_abs_FILE ${_file} ABSOLUTE)
|
||||
if(_ext MATCHES "ts")
|
||||
list(APPEND _my_tsfiles ${_abs_FILE})
|
||||
else()
|
||||
list(APPEND _my_sources ${_abs_FILE})
|
||||
endif()
|
||||
endforeach()
|
||||
foreach(_ts_file ${_my_tsfiles})
|
||||
set(_lst_file_srcs)
|
||||
if(_my_sources)
|
||||
# Qt made a file listing all sources and used that as an argument, but pylupdate5 doesn't support that.
|
||||
# Qt allowed directories to be listed as sources, but pylupdate5 requires their contents to be listed.
|
||||
get_filename_component(_ts_name ${_ts_file} NAME_WE)
|
||||
set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file")
|
||||
foreach(_lst_file_src ${_my_sources})
|
||||
if(IS_DIRECTORY ${_lst_file_src})
|
||||
file(GLOB _directory_contents ${_lst_file_src}/*.py ${_lst_file_src}/*.ui)
|
||||
list(APPEND _lst_file_srcs ${_directory_contents})
|
||||
else()
|
||||
list(APPEND _lst_file_srcs ${_lst_file_src})
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
add_custom_command(OUTPUT ${_ts_file}
|
||||
COMMAND ${PYTHON_ROOT}/pylupdate5.bat
|
||||
ARGS ${_lupdate_options} ${_lst_file_srcs} -ts ${_ts_file}
|
||||
DEPENDS ${_lst_file_srcs} ${_ts_lst_file}
|
||||
WORKING_DIRECTORY ${PYTHON_ROOT}
|
||||
VERBATIM)
|
||||
endforeach()
|
||||
qt5_add_translation(${_qm_files} ${_my_tsfiles})
|
||||
set(${_qm_files} ${${_qm_files}} PARENT_SCOPE)
|
||||
endfunction()
|
||||
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS><TS version="2.0">
|
||||
<context>
|
||||
<name>DDSFile</name>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="33"/>
|
||||
<source>Magic number mismatch.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="88"/>
|
||||
<source>{0} (equivalent to {1})</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="93"/>
|
||||
<source>Red bitmask {0}, Green bitmask {1}, Blue bitmask {2}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="97"/>
|
||||
<source>Luminance bitmask {0}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="101"/>
|
||||
<source>Alpha bitmask {0}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="103"/>
|
||||
<source>{0}×{1}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="105"/>
|
||||
<source>Cubemap</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="105"/>
|
||||
<source>2D</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="107"/>
|
||||
<source>Mipmapped</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="107"/>
|
||||
<source>No mipmaps</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="109"/>
|
||||
<source>{0}, {1} {2}, {3}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="131"/>
|
||||
<source>OpenGL driver incompatible with texture format.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDS/DDSFile.py" line="137"/>
|
||||
<source>Cubemap faces must be square</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DDSPreview</name>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="329"/>
|
||||
<source>Lets you preview DDS files by actually uploading them to the GPU.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="338"/>
|
||||
<source>If enabled, log OpenGL errors and debug messages. May decrease performance.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="339"/>
|
||||
<source>Red channel of background colour</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="340"/>
|
||||
<source>Green channel of background colour</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="341"/>
|
||||
<source>Blue channel of background colour</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="342"/>
|
||||
<source>Alpha channel of background colour</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="377"/>
|
||||
<source>Pick background colour</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>DDSWidget</name>
|
||||
<message>
|
||||
<location filename="DDSPreview.py" line="191"/>
|
||||
<source>OpenGL debug message: {0}</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
Reference in New Issue
Block a user