Initial implementation which should have been added to version control ages ago

This commit is contained in:
AnyOldName3
2019-10-01 01:33:01 +01:00
parent b34621513b
commit dc14039c8a
10 changed files with 1720 additions and 0 deletions
+11
View File
@@ -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)
+17
View File
@@ -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)
+14
View File
@@ -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
+193
View File
@@ -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)
View File
+201
View File
@@ -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
+396
View File
@@ -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()
+88
View File
@@ -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()
+117
View File
@@ -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}&#xc3;&#x97;{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>