/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2016-2018 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #include #include #include #include "QRDUtils.h" static QVariant interpret(const ResourceFormat &f, uint16_t comp) { if(f.compByteWidth != 2 || f.compType == CompType::Float) return QVariant(); if(f.compType == CompType::SInt) { return (int16_t)comp; } else if(f.compType == CompType::UInt) { return comp; } else if(f.compType == CompType::SScaled) { return (float)((int16_t)comp); } else if(f.compType == CompType::UScaled) { return (float)comp; } else if(f.compType == CompType::UNorm) { return (float)comp / (float)0xffff; } else if(f.compType == CompType::SNorm) { int16_t cast = (int16_t)comp; float ret = -1.0f; if(cast == -32768) ret = -1.0f; else ret = ((float)cast) / 32767.0f; return ret; } return QVariant(); } static QVariant interpret(const ResourceFormat &f, byte comp) { if(f.compByteWidth != 1 || f.compType == CompType::Float) return QVariant(); if(f.compType == CompType::SInt) { return (int8_t)comp; } else if(f.compType == CompType::UInt) { return comp; } else if(f.compType == CompType::SScaled) { return (float)((int8_t)comp); } else if(f.compType == CompType::UScaled) { return (float)comp; } else if(f.compType == CompType::UNorm) { return ((float)comp) / 255.0f; } else if(f.compType == CompType::SNorm) { int8_t cast = (int8_t)comp; float ret = -1.0f; if(cast == -128) ret = -1.0f; else ret = ((float)cast) / 127.0f; return ret; } return QVariant(); } FormatElement::FormatElement() { buffer = 0; offset = 0; perinstance = false; instancerate = 1; rowmajor = false; matrixdim = 0; hex = false; rgb = false; systemValue = ShaderBuiltin::Undefined; } FormatElement::FormatElement(const QString &Name, int buf, uint offs, bool perInst, int instRate, bool rowMat, uint matDim, ResourceFormat f, bool hexDisplay, bool rgbDisplay) { name = Name; buffer = buf; offset = offs; format = f; perinstance = perInst; instancerate = instRate; rowmajor = rowMat; matrixdim = matDim; hex = hexDisplay; rgb = rgbDisplay; systemValue = ShaderBuiltin::Undefined; } QList FormatElement::ParseFormatString(const QString &formatString, uint64_t maxLen, bool tightPacking, QString &errors) { QList elems; // regex doesn't account for trailing or preceeding whitespace, or comments QRegularExpression regExpr( lit("^" // start of the line "(row_major\\s+)?" // row_major matrix "(rgb\\s+)?" // rgb element colourising "(" // group the options for the type "uintten|unormten" // R10G10B10A2 types "|floateleven" // R11G11B10 special type "|unormh|unormb" // UNORM 16-bit and 8-bit types "|snormh|snormb" // SNORM 16-bit and 8-bit types "|bool" // bool is stored as 4-byte int "|byte|short|int" // signed ints "|ubyte|ushort|uint" // unsigned ints "|xbyte|xshort|xint" // hex ints "|half|float|double" // float types "|vec|uvec|ivec" // OpenGL vector types "|mat|umat|imat" // OpenGL matrix types ")" // end of the type group "([1-9])?" // might be a vector "(x[1-9])?" // or a matrix "(\\s+[A-Za-z_][A-Za-z0-9_]*)?" // get identifier name "(\\[[0-9]+\\])?" // optional array dimension "(\\s*:\\s*[A-Za-z_][A-Za-z0-9_]*)?" // optional semantic "$")); bool success = true; errors = QString(); QString text = formatString; text = text.replace(lit("{"), QString()).replace(lit("}"), QString()); QRegularExpression c_comments(lit("/\\*[^*]*\\*+(?:[^*/][^*]*\\*+)*/")); QRegularExpression cpp_comments(lit("//.*")); text = text.replace(c_comments, QString()).replace(cpp_comments, QString()); uint32_t offset = 0; // get each line and parse it to determine the format the user wanted for(QString &l : text.split(QLatin1Char(';'))) { QString line = l.trimmed(); if(line.isEmpty()) continue; QRegularExpressionMatch match = regExpr.match(line); if(!match.hasMatch()) { errors = tr("Couldn't parse line: %1\n").arg(line); success = false; break; } QString basetype = match.captured(3); bool row_major = !match.captured(1).isEmpty(); bool rgb = !match.captured(2).isEmpty(); QString vectorDim = !match.captured(4).isEmpty() ? match.captured(4) : lit("1"); QString matrixDim = !match.captured(5).isEmpty() ? match.captured(5).mid(1) : lit("1"); QString name = !match.captured(6).isEmpty() ? match.captured(6).trimmed() : lit("data"); QString arrayDim = !match.captured(7).isEmpty() ? match.captured(7).trimmed() : lit("[1]"); arrayDim = arrayDim.mid(1, arrayDim.count() - 2); if(!match.captured(5).isEmpty()) { vectorDim.swap(matrixDim); } ResourceFormat fmt; fmt.compType = CompType::Typeless; bool hex = false; CompType type = CompType::Float; uint32_t count = 0; uint32_t arrayCount = 1; uint32_t matrixCount = 0; uint32_t width = 0; // check for square matrix declarations like 'mat4' and 'mat3' if(basetype == lit("mat") && match.captured(5).isEmpty()) matrixDim = vectorDim; // calculate format { bool ok = false; count = vectorDim.toUInt(&ok); if(!ok) { errors = tr("Invalid vector dimension on line: %1\n").arg(line); success = false; break; } arrayCount = arrayDim.toUInt(&ok); if(!ok) { arrayCount = 1; } arrayCount = qMax(0U, arrayCount); matrixCount = matrixDim.toUInt(&ok); if(!ok) { errors = tr("Invalid matrix second dimension on line: %1\n").arg(line); success = false; break; } if(basetype == lit("bool")) { type = CompType::UInt; width = 4; } else if(basetype == lit("byte")) { type = CompType::SInt; width = 1; } else if(basetype == lit("ubyte") || basetype == lit("xbyte")) { type = CompType::UInt; width = 1; } else if(basetype == lit("short")) { type = CompType::SInt; width = 2; } else if(basetype == lit("ushort") || basetype == lit("xshort")) { type = CompType::UInt; width = 2; } else if(basetype == lit("int") || basetype == lit("ivec") || basetype == lit("imat")) { type = CompType::SInt; width = 4; } else if(basetype == lit("uint") || basetype == lit("xint") || basetype == lit("uvec") || basetype == lit("umat")) { type = CompType::UInt; width = 4; } else if(basetype == lit("half")) { type = CompType::Float; width = 2; } else if(basetype == lit("float") || basetype == lit("vec") || basetype == lit("mat")) { type = CompType::Float; width = 4; } else if(basetype == lit("double")) { type = CompType::Double; width = 8; } else if(basetype == lit("unormh")) { type = CompType::UNorm; width = 2; } else if(basetype == lit("unormb")) { type = CompType::UNorm; width = 1; } else if(basetype == lit("snormh")) { type = CompType::SNorm; width = 2; } else if(basetype == lit("snormb")) { type = CompType::SNorm; width = 1; } else if(basetype == lit("uintten")) { fmt.compType = CompType::UInt; fmt.compCount = 4 * count; fmt.compByteWidth = 1; fmt.type = ResourceFormatType::R10G10B10A2; } else if(basetype == lit("unormten")) { fmt.compType = CompType::UInt; fmt.compCount = 4 * count; fmt.compByteWidth = 1; fmt.type = ResourceFormatType::R10G10B10A2; } else if(basetype == lit("floateleven")) { fmt.compType = CompType::Float; fmt.compCount = 3 * count; fmt.compByteWidth = 1; fmt.type = ResourceFormatType::R11G11B10; } else { errors = tr("Unrecognised basic type on line: %1\n").arg(line); success = false; break; } } if(basetype == lit("xint") || basetype == lit("xshort") || basetype == lit("xbyte")) hex = true; if(fmt.compType == CompType::Typeless) { fmt.compType = type; fmt.compCount = count; fmt.compByteWidth = width; } if(arrayCount == 1) { FormatElement elem(name, 0, offset, false, 1, row_major, matrixCount, fmt, hex, rgb); uint32_t advance = elem.byteSize(); if(!tightPacking) { // cbuffer packing always works in floats advance = (advance + 3U) & (~3U); // cbuffer packing doesn't allow elements to cross float4 boundaries, nudge up if this was // the case if(offset / 16 != (offset + elem.byteSize() - 1) / 16) { elem.offset = offset = (offset + 0xFU) & (~0xFU); } } elems.push_back(elem); offset += advance; } else { // when cbuffer packing, arrays are always aligned at float4 boundary if(!tightPacking) { if(offset % 16 != 0) { offset = (offset + 0xFU) & (~0xFU); } } for(uint a = 0; a < arrayCount; a++) { FormatElement elem(QFormatStr("%1[%2]").arg(name).arg(a), 0, offset, false, 1, row_major, matrixCount, fmt, hex, rgb); elems.push_back(elem); uint32_t advance = elem.byteSize(); // cbuffer packing each array element is always float4 aligned if(!tightPacking) { advance = (advance + 0xFU) & (~0xFU); } offset += advance; } } } if(!success || elems.isEmpty()) { elems.clear(); ResourceFormat fmt; fmt.compType = CompType::UInt; fmt.compByteWidth = 4; fmt.compCount = 4; if(maxLen > 0 && maxLen < 16) fmt.compCount = 1; if(maxLen > 0 && maxLen < 4) fmt.compByteWidth = 1; elems.push_back(FormatElement(lit("data"), 0, 0, false, 1, false, 1, fmt, true, false)); } return elems; } template inline T readObj(const byte *&data, const byte *end, bool &ok) { if(data + sizeof(T) > end) { ok = false; return T(); } T ret = *(T *)data; data += sizeof(T); return ret; } QVariantList FormatElement::GetVariants(const byte *&data, const byte *end) const { QVariantList ret; bool ok = true; if(format.type == ResourceFormatType::R5G5B5A1) { uint16_t packed = readObj(data, end, ok); ret.push_back((float)((packed >> 0) & 0x1f) / 31.0f); ret.push_back((float)((packed >> 5) & 0x1f) / 31.0f); ret.push_back((float)((packed >> 10) & 0x1f) / 31.0f); ret.push_back(((packed & 0x8000) > 0) ? 1.0f : 0.0f); if(format.bgraOrder) { QVariant tmp = ret[2]; ret[2] = ret[0]; ret[0] = tmp; } } else if(format.type == ResourceFormatType::R5G6B5) { uint16_t packed = readObj(data, end, ok); ret.push_back((float)((packed >> 0) & 0x1f) / 31.0f); ret.push_back((float)((packed >> 5) & 0x3f) / 63.0f); ret.push_back((float)((packed >> 11) & 0x1f) / 31.0f); if(format.bgraOrder) { QVariant tmp = ret[2]; ret[2] = ret[0]; ret[0] = tmp; } } else if(format.type == ResourceFormatType::R4G4B4A4) { uint16_t packed = readObj(data, end, ok); ret.push_back((float)((packed >> 0) & 0xf) / 15.0f); ret.push_back((float)((packed >> 4) & 0xf) / 15.0f); ret.push_back((float)((packed >> 8) & 0xf) / 15.0f); ret.push_back((float)((packed >> 12) & 0xf) / 15.0f); if(format.bgraOrder) { QVariant tmp = ret[2]; ret[2] = ret[0]; ret[0] = tmp; } } else if(format.type == ResourceFormatType::R10G10B10A2) { // allow for vectors of this format - for raw buffer viewer for(int i = 0; i < int(format.compCount / 4); i++) { uint32_t packed = readObj(data, end, ok); uint32_t r = (packed >> 0) & 0x3ff; uint32_t g = (packed >> 10) & 0x3ff; uint32_t b = (packed >> 20) & 0x3ff; uint32_t a = (packed >> 30) & 0x003; if(format.bgraOrder) { uint32_t tmp = b; b = r; r = tmp; } if(format.compType == CompType::UInt) { ret.push_back(r); ret.push_back(g); ret.push_back(b); ret.push_back(a); } else if(format.compType == CompType::UScaled) { ret.push_back((float)r); ret.push_back((float)g); ret.push_back((float)b); ret.push_back((float)a); } else if(format.compType == CompType::SInt || format.compType == CompType::SScaled || format.compType == CompType::SNorm) { int ir, ig, ib, ia; // interpret RGB as 10-bit signed integers if(r <= 511) ir = (int)r; else ir = ((int)r) - 1024; if(g <= 511) ig = (int)g; else ig = ((int)g) - 1024; if(b <= 511) ib = (int)b; else ib = ((int)b) - 1024; // 2-bit signed integer if(a <= 1) ia = (int)a; else ia = ((int)a) - 4; if(format.compType == CompType::SInt) { ret.push_back(ir); ret.push_back(ig); ret.push_back(ib); ret.push_back(ia); } else if(format.compType == CompType::SScaled) { ret.push_back((float)ir); ret.push_back((float)ig); ret.push_back((float)ib); ret.push_back((float)ia); } else if(format.compType == CompType::SNorm) { if(ir == -512) ir = -511; if(ig == -512) ig = -511; if(ib == -512) ib = -511; if(ia == -2) ia = -1; ret.push_back((float)ir / 511.0f); ret.push_back((float)ig / 511.0f); ret.push_back((float)ib / 511.0f); ret.push_back((float)ia / 1.0f); } } else { ret.push_back((float)r / 1023.0f); ret.push_back((float)g / 1023.0f); ret.push_back((float)b / 1023.0f); ret.push_back((float)a / 3.0f); } } } else if(format.type == ResourceFormatType::R11G11B10) { uint32_t packed = readObj(data, end, ok); uint32_t mantissas[] = { (packed >> 0) & 0x3f, (packed >> 11) & 0x3f, (packed >> 22) & 0x1f, }; int32_t exponents[] = { int32_t(packed >> 6) & 0x1f, int32_t(packed >> 17) & 0x1f, int32_t(packed >> 27) & 0x1f, }; static const uint32_t leadbit[] = { 0x40, 0x40, 0x20, }; for(int i = 0; i < 3; i++) { if(mantissas[i] == 0 && exponents[i] == 0) { ret.push_back((float)0.0f); } else { if(exponents[i] == 0x1f) { // no sign bit, can't be negative infinity if(mantissas[i] == 0) ret.push_back((float)qInf()); else ret.push_back((float)qQNaN()); } else if(exponents[i] != 0) { // normal value, add leading bit uint32_t combined = leadbit[i] | mantissas[i]; // calculate value ret.push_back(((float)combined / (float)leadbit[i]) * qPow(2.0f, (float)exponents[i] - 15.0f)); } else if(exponents[i] == 0) { // we know xMantissa isn't 0 also, or it would have been caught above so // this is a subnormal value, pretend exponent is 1 and don't add leading bit ret.push_back(((float)mantissas[i] / (float)leadbit[i]) * qPow(2.0f, (float)1.0f - 15.0f)); } } } } else { int dim = (int)(qMax(matrixdim, 1U) * format.compCount); for(int i = 0; i < dim; i++) { if(format.compType == CompType::Float) { if(format.compByteWidth == 8) ret.push_back(readObj(data, end, ok)); else if(format.compByteWidth == 4) ret.push_back(readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back(RENDERDOC_HalfToFloat(readObj(data, end, ok))); } else if(format.compType == CompType::SInt) { if(format.compByteWidth == 4) ret.push_back((int)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((int)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((int)readObj(data, end, ok)); } else if(format.compType == CompType::UInt) { if(format.compByteWidth == 4) ret.push_back((uint32_t)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((uint32_t)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((uint32_t)readObj(data, end, ok)); } else if(format.compType == CompType::UScaled) { if(format.compByteWidth == 4) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((float)readObj(data, end, ok)); } else if(format.compType == CompType::SScaled) { if(format.compByteWidth == 4) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 2) ret.push_back((float)readObj(data, end, ok)); else if(format.compByteWidth == 1) ret.push_back((float)readObj(data, end, ok)); } else if(format.compType == CompType::Depth) { if(format.compByteWidth == 4) { // 32-bit depth is native floats ret.push_back(readObj(data, end, ok)); } else if(format.compByteWidth == 3) { // 32-bit depth is normalised, masked against non-stencil bits uint32_t f = readObj(data, end, ok); f &= 0x00ffffff; ret.push_back((float)f / (float)0x00ffffff); } else if(format.compByteWidth == 2) { // 16-bit depth is normalised float f = (float)readObj(data, end, ok); ret.push_back(f / (float)0x0000ffff); } } else if(format.compType == CompType::Double) { ret.push_back(readObj(data, end, ok)); } else { // unorm/snorm if(format.compByteWidth == 4) { // should never hit this - no 32bit unorm/snorm type qCritical() << "Unexpected 4-byte unorm/snorm value"; ret.push_back((float)readObj(data, end, ok) / (float)0xffffffff); } else if(format.compByteWidth == 2) { ret.push_back(interpret(format, readObj(data, end, ok))); } else if(format.compByteWidth == 1) { ret.push_back(interpret(format, readObj(data, end, ok))); } } } if(format.bgraOrder) { QVariant tmp = ret[2]; ret[2] = ret[0]; ret[0] = tmp; } } // we read off the end, return empty set if(!ok) ret.clear(); return ret; } ShaderVariable FormatElement::GetShaderVar(const byte *&data, const byte *end) const { QVariantList objs = GetVariants(data, end); ShaderVariable ret; ret.name = name.toUtf8().data(); ret.type = VarType::Float; if(format.compType == CompType::UInt) ret.type = VarType::UInt; if(format.compType == CompType::SInt) ret.type = VarType::Int; if(format.compType == CompType::Double) ret.type = VarType::Double; ret.columns = qMin(format.compCount, uint8_t(4)); ret.rows = qMin(matrixdim, 4U); ret.displayAsHex = hex; for(uint32_t row = 0; row < ret.rows; row++) { for(uint32_t col = 0; col < ret.columns; col++) { uint32_t dst = row * ret.columns + col; uint32_t src = row * format.compCount + col; // if we partially read a failure, reset the variable and return if((int)src >= objs.size()) { ret.name = "-"; memset(ret.value.dv, 0, sizeof(ret.value.dv)); return ret; } const QVariant &o = objs[src]; if(ret.type == VarType::Double) ret.value.dv[dst] = o.toDouble(); else if(ret.type == VarType::UInt) ret.value.uv[dst] = o.toUInt(); else if(ret.type == VarType::Int) ret.value.iv[dst] = o.toInt(); else ret.value.fv[dst] = o.toFloat(); } } return ret; } uint32_t FormatElement::byteSize() const { uint32_t vecSize = format.compByteWidth * format.compCount; if(format.type == ResourceFormatType::R5G5B5A1 || format.type == ResourceFormatType::R5G6B5 || format.type == ResourceFormatType::R4G4B4A4) vecSize = 2; if(format.type == ResourceFormatType::R10G10B10A2 || format.type == ResourceFormatType::R11G11B10) vecSize = 4; return vecSize * matrixdim; } QString TypeString(const ShaderVariable &v) { if(!v.members.isEmpty() || v.isStruct) { if(v.isStruct) return lit("struct"); else return QFormatStr("%1[%2]").arg(TypeString(v.members[0])).arg(v.members.count()); } QString typeStr = ToQStr(v.type); if(v.displayAsHex && v.type == VarType::UInt) typeStr = lit("xint"); if(v.rows == 1 && v.columns == 1) return typeStr; if(v.rows == 1) return QFormatStr("%1%2").arg(typeStr).arg(v.columns); else return QFormatStr("%1%2x%3 (%4)") .arg(typeStr) .arg(v.rows) .arg(v.columns) .arg(v.rowMajor ? QApplication::tr("row major", "FormatElement") : QApplication::tr("column major", "FormatElement")); } template static QString RowValuesToString(int cols, el x, el y, el z, el w) { if(cols == 1) return Formatter::Format(x); else if(cols == 2) return QFormatStr("%1, %2").arg(Formatter::Format(x)).arg(Formatter::Format(y)); else if(cols == 3) return QFormatStr("%1, %2, %3") .arg(Formatter::Format(x)) .arg(Formatter::Format(y)) .arg(Formatter::Format(z)); else return QFormatStr("%1, %2, %3, %4") .arg(Formatter::Format(x)) .arg(Formatter::Format(y)) .arg(Formatter::Format(z)) .arg(Formatter::Format(w)); } QString RowString(const ShaderVariable &v, uint32_t row, VarType type) { if(type == VarType::Unknown) type = v.type; if(type == VarType::Double) return RowValuesToString((int)v.columns, v.value.dv[row * v.columns + 0], v.value.dv[row * v.columns + 1], v.value.dv[row * v.columns + 2], v.value.dv[row * v.columns + 3]); else if(type == VarType::Int) return RowValuesToString((int)v.columns, v.value.iv[row * v.columns + 0], v.value.iv[row * v.columns + 1], v.value.iv[row * v.columns + 2], v.value.iv[row * v.columns + 3]); else if(type == VarType::UInt) return RowValuesToString((int)v.columns, v.value.uv[row * v.columns + 0], v.value.uv[row * v.columns + 1], v.value.uv[row * v.columns + 2], v.value.uv[row * v.columns + 3]); else return RowValuesToString((int)v.columns, v.value.fv[row * v.columns + 0], v.value.fv[row * v.columns + 1], v.value.fv[row * v.columns + 2], v.value.fv[row * v.columns + 3]); } QString VarString(const ShaderVariable &v) { if(!v.members.isEmpty()) return QString(); if(v.rows == 1) return RowString(v, 0); QString ret; for(int i = 0; i < (int)v.rows; i++) { if(i > 0) ret += lit("\n"); ret += lit("{") + RowString(v, i) + lit("}"); } return ret; } QString RowTypeString(const ShaderVariable &v) { if(!v.members.isEmpty() || v.isStruct) { if(v.isStruct) return lit("struct"); else return lit("flibbertygibbet"); } if(v.rows == 0 && v.columns == 0) return lit("-"); QString typeStr = ToQStr(v.type); if(v.displayAsHex && v.type == VarType::UInt) typeStr = lit("xint"); if(v.columns == 1) return typeStr; return QFormatStr("%1%2").arg(typeStr).arg(v.columns); }