Files
renderdoc/renderdoc/replay/replay_driver.cpp
T

1597 lines
55 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2021 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 "replay_driver.h"
#include "compressonator/CMP_Core.h"
#include "maths/formatpacking.h"
#include "maths/half_convert.h"
#include "serialise/serialiser.h"
template <>
rdcstr DoStringise(const RemapTexture &el)
{
BEGIN_ENUM_STRINGISE(RemapTexture);
{
STRINGISE_ENUM_CLASS(NoRemap)
STRINGISE_ENUM_CLASS(RGBA8)
STRINGISE_ENUM_CLASS(RGBA16)
STRINGISE_ENUM_CLASS(RGBA32)
}
END_ENUM_STRINGISE();
}
template <typename SerialiserType>
void DoSerialise(SerialiserType &ser, GetTextureDataParams &el)
{
SERIALISE_MEMBER(forDiskSave);
SERIALISE_MEMBER(standardLayout);
SERIALISE_MEMBER(typeCast);
SERIALISE_MEMBER(resolve);
SERIALISE_MEMBER(remap);
SERIALISE_MEMBER(blackPoint);
SERIALISE_MEMBER(whitePoint);
}
INSTANTIATE_SERIALISE_TYPE(GetTextureDataParams);
static bool PreviousNextExcludedMarker(DrawcallDescription *draw)
{
return bool(draw->flags & (DrawFlags::PushMarker | DrawFlags::SetMarker | DrawFlags::MultiDraw |
DrawFlags::APICalls));
}
CompType BaseRemapType(CompType typeCast)
{
switch(typeCast)
{
case CompType::Float:
case CompType::UNorm:
case CompType::SNorm:
case CompType::UNormSRGB: return CompType::Float;
case CompType::UInt: return CompType::UInt;
case CompType::SInt: return CompType::SInt;
default: return typeCast;
}
}
static DrawcallDescription *SetupDrawcallPointers(rdcarray<DrawcallDescription *> &drawcallTable,
rdcarray<DrawcallDescription> &draws,
DrawcallDescription *parent,
DrawcallDescription *&previous)
{
DrawcallDescription *ret = NULL;
for(size_t i = 0; i < draws.size(); i++)
{
DrawcallDescription *draw = &draws[i];
draw->parent = parent;
if(!draw->children.empty())
{
{
RDCASSERT(drawcallTable.empty() || draw->eventId > drawcallTable.back()->eventId);
drawcallTable.resize(RDCMAX(drawcallTable.size(), size_t(draw->eventId) + 1));
drawcallTable[draw->eventId] = draw;
}
ret = SetupDrawcallPointers(drawcallTable, draw->children, draw, previous);
}
else if(PreviousNextExcludedMarker(draw))
{
// don't want to set up previous/next links for markers, but still add them to the table
// Some markers like Present should have previous/next, but API Calls we also skip
{
// we also allow equal EIDs for fake markers that don't have their own EIDs
RDCASSERT(drawcallTable.empty() || draw->eventId > drawcallTable.back()->eventId ||
(draw->eventId == drawcallTable.back()->eventId &&
(drawcallTable.back()->flags & DrawFlags::PushMarker)));
drawcallTable.resize(RDCMAX(drawcallTable.size(), size_t(draw->eventId) + 1));
drawcallTable[draw->eventId] = draw;
}
}
else
{
if(previous)
previous->next = draw;
draw->previous = previous;
{
// we also allow equal EIDs for fake markers that don't have their own EIDs
RDCASSERT(drawcallTable.empty() || draw->eventId > drawcallTable.back()->eventId ||
(draw->eventId == drawcallTable.back()->eventId &&
(drawcallTable.back()->flags & DrawFlags::PushMarker)));
drawcallTable.resize(RDCMAX(drawcallTable.size(), size_t(draw->eventId) + 1));
drawcallTable[draw->eventId] = draw;
}
ret = previous = draw;
}
}
return ret;
}
void SetupDrawcallPointers(rdcarray<DrawcallDescription *> &drawcallTable,
rdcarray<DrawcallDescription> &draws)
{
DrawcallDescription *previous = NULL;
SetupDrawcallPointers(drawcallTable, draws, NULL, previous);
// markers don't enter the previous/next chain, but we still want pointers for them that point to
// the next or previous actual draw (skipping any markers). This means that draw->next->previous
// != draw sometimes, but it's more useful than draw->next being NULL in the middle of the list.
// This enables searching for a marker string and then being able to navigate from there and
// joining the 'real' linked list after one step.
previous = NULL;
rdcarray<DrawcallDescription *> markers;
for(DrawcallDescription *draw : drawcallTable)
{
if(!draw)
continue;
bool marker = PreviousNextExcludedMarker(draw);
if(marker)
{
// point the previous pointer to the last non-marker draw we got. If we haven't hit one yet
// because this is near the start, this will just be NULL.
draw->previous = previous;
// because there can be multiple markers consecutively we want to point all of their nexts to
// the next draw we encounter. Accumulate this list, though in most cases it will only be 1
// long as it's uncommon to have multiple markers one after the other
markers.push_back(draw);
}
else
{
// the next markers we encounter should point their previous to this.
previous = draw;
// all previous markers point to this one
for(DrawcallDescription *m : markers)
m->next = draw;
markers.clear();
}
}
}
void PatchLineStripIndexBuffer(const DrawcallDescription *draw, Topology topology, uint8_t *idx8,
uint16_t *idx16, uint32_t *idx32, rdcarray<uint32_t> &patchedIndices)
{
const uint32_t restart = 0xffffffff;
#define IDX_VALUE(offs) \
(idx16 ? idx16[index + (offs)] \
: (idx32 ? idx32[index + (offs)] : (idx8 ? idx8[index + (offs)] : index + (offs))))
switch(topology)
{
case Topology::TriangleList:
{
for(uint32_t index = 0; index + 3 <= draw->numIndices; index += 3)
{
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(IDX_VALUE(1));
patchedIndices.push_back(IDX_VALUE(2));
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(restart);
}
break;
}
case Topology::TriangleStrip:
{
// we decompose into individual triangles. This will mean the shared lines will be overwritten
// twice but it's a simple algorithm and otherwise decomposing a tristrip into a line strip
// would need some more complex handling (you could two pairs of triangles in a single strip
// by changing the winding, but then you'd need to restart and jump back, and handle a
// trailing single triangle, etc).
for(uint32_t index = 0; index + 3 <= draw->numIndices; index++)
{
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(IDX_VALUE(1));
patchedIndices.push_back(IDX_VALUE(2));
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(restart);
}
break;
}
case Topology::TriangleFan:
{
uint32_t index = 0;
uint32_t base = IDX_VALUE(0);
index++;
// this would be easier to do as a line list and just do base -> 1, 1 -> 2 lines for each
// triangle then a base -> 2 for the last one. However I would be amazed if this code ever
// runs except in an artificial test, so let's go with the simple and easy to understand
// solution.
for(; index + 2 <= draw->numIndices; index++)
{
patchedIndices.push_back(base);
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(IDX_VALUE(1));
patchedIndices.push_back(base);
patchedIndices.push_back(restart);
}
break;
}
case Topology::TriangleList_Adj:
{
// skip the adjacency values
for(uint32_t index = 0; index + 6 <= draw->numIndices; index += 6)
{
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(IDX_VALUE(2));
patchedIndices.push_back(IDX_VALUE(4));
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(restart);
}
break;
}
case Topology::TriangleStrip_Adj:
{
// skip the adjacency values
for(uint32_t index = 0; index + 6 <= draw->numIndices; index += 2)
{
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(IDX_VALUE(2));
patchedIndices.push_back(IDX_VALUE(4));
patchedIndices.push_back(IDX_VALUE(0));
patchedIndices.push_back(restart);
}
break;
}
default:
RDCERR("Unsupported topology %s for line-list patching", ToStr(topology).c_str());
return;
}
#undef IDX_VALUE
}
void PatchTriangleFanRestartIndexBufer(rdcarray<uint32_t> &patchedIndices, uint32_t restartIndex)
{
if(patchedIndices.empty())
return;
rdcarray<uint32_t> newIndices;
uint32_t firstIndex = patchedIndices[0];
size_t i = 1;
// while we have at least two indices left
while(i + 1 < patchedIndices.size())
{
uint32_t a = patchedIndices[i];
uint32_t b = patchedIndices[i + 1];
if(a != restartIndex && b != restartIndex)
{
// no restart, add primitive
newIndices.push_back(firstIndex);
newIndices.push_back(a);
newIndices.push_back(b);
i++;
continue;
}
else if(b == restartIndex)
{
// we've already added the last triangle before the restart in the previous iteration, just
// continue so we hit the a == restartIndex case below
i++;
}
else if(a == restartIndex)
{
// new first index is b
firstIndex = b;
// skip both the restartIndex value and the first index, and begin at the next real index (if
// it exists)
i += 2;
uint32_t next[2] = {b, b};
// if this is the last vertex, the triangle will be degenerate
if(i < patchedIndices.size())
next[0] = patchedIndices[i];
if(i + 1 < patchedIndices.size())
next[1] = patchedIndices[i + 1];
// output 3 dummy degenerate triangles so vertex ID mapping is easy
// we rotate the triangles so the important vertex is last in each.
for(size_t dummy = 0; dummy < 3; dummy++)
{
newIndices.push_back(restartIndex);
newIndices.push_back(restartIndex);
newIndices.push_back(restartIndex);
}
}
}
newIndices.swap(patchedIndices);
}
void StandardFillCBufferVariable(ResourceId shader, const ShaderConstantDescriptor &desc,
uint32_t dataOffset, const bytebuf &data, ShaderVariable &outvar,
uint32_t matStride)
{
const VarType type = outvar.type;
const uint32_t rows = outvar.rows;
const uint32_t cols = outvar.columns;
size_t elemByteSize = 4;
if(type == VarType::Double || type == VarType::ULong || type == VarType::SLong ||
type == VarType::GPUPointer)
elemByteSize = 8;
else if(type == VarType::Half || type == VarType::UShort || type == VarType::SShort)
elemByteSize = 2;
else if(type == VarType::UByte || type == VarType::SByte)
elemByteSize = 1;
// primary is the 'major' direction
// so a matrix is a secondaryDim number of primaryDim-sized vectors
uint32_t primaryDim = cols;
uint32_t secondaryDim = rows;
if(rows > 1 && !outvar.rowMajor)
{
primaryDim = rows;
secondaryDim = cols;
}
if(dataOffset < data.size())
{
const byte *srcData = data.data() + dataOffset;
const size_t avail = data.size() - dataOffset;
byte *dstData = outvar.value.u8v.data();
const size_t dstStride = elemByteSize == 8 ? 8 : 4;
// each secondaryDim element (row or column) is stored in a primaryDim-vector.
// We copy each vector member individually to account for smaller than uint32 sized types.
for(uint32_t s = 0; s < secondaryDim; s++)
{
for(uint32_t p = 0; p < primaryDim; p++)
{
const size_t srcOffset = matStride * s + p * elemByteSize;
const size_t dstOffset = (primaryDim * s + p) * dstStride;
if(srcOffset + elemByteSize <= avail)
memcpy(dstData + dstOffset, srcData + srcOffset, elemByteSize);
}
}
// if it's a matrix and not row major, transpose
if(primaryDim > 1 && secondaryDim > 1 && !outvar.rowMajor)
{
ShaderVariable tmp = outvar;
if(elemByteSize == 8)
{
for(size_t ri = 0; ri < rows; ri++)
for(size_t ci = 0; ci < cols; ci++)
outvar.value.u64v[ri * cols + ci] = tmp.value.u64v[ci * rows + ri];
}
else
{
for(size_t ri = 0; ri < rows; ri++)
for(size_t ci = 0; ci < cols; ci++)
outvar.value.u32v[ri * cols + ci] = tmp.value.u32v[ci * rows + ri];
}
}
// special case - decode halfs in-place, sign extend signed < 4 byte integers
if(type == VarType::Half)
{
for(size_t ri = 0; ri < rows; ri++)
{
for(size_t ci = 0; ci < cols; ci++)
{
outvar.value.f32v[ri * cols + ci] =
ConvertFromHalf((uint16_t)outvar.value.u32v[ri * cols + ci]);
}
}
}
else if(type == VarType::SShort || type == VarType::SByte)
{
const uint32_t testMask = (type == VarType::SShort ? 0x8000 : 0x80);
const uint32_t extendMask = (type == VarType::SShort ? 0xffff0000 : 0xffffff00);
for(size_t ri = 0; ri < rows; ri++)
{
for(size_t ci = 0; ci < cols; ci++)
{
uint32_t &u = outvar.value.u32v[ri * cols + ci];
if(u & testMask)
u |= extendMask;
}
}
}
}
if(desc.pointerTypeID != ~0U)
outvar.SetTypedPointer(outvar.value.u64v[0], shader, desc.pointerTypeID);
}
static void StandardFillCBufferVariables(ResourceId shader, const rdcarray<ShaderConstant> &invars,
rdcarray<ShaderVariable> &outvars, const bytebuf &data,
uint32_t baseOffset)
{
for(size_t v = 0; v < invars.size(); v++)
{
rdcstr basename = invars[v].name;
uint8_t rows = invars[v].type.descriptor.rows;
uint8_t cols = invars[v].type.descriptor.columns;
uint32_t elems = RDCMAX(1U, invars[v].type.descriptor.elements);
const bool rowMajor = invars[v].type.descriptor.rowMajorStorage != 0;
const bool isArray = elems > 1;
const uint32_t matStride = invars[v].type.descriptor.matrixByteStride;
uint32_t dataOffset = baseOffset + invars[v].byteOffset;
if(!invars[v].type.members.empty() || (rows == 0 && cols == 0))
{
ShaderVariable var;
var.name = basename;
var.rows = var.columns = 0;
var.type = VarType::Float;
var.rowMajor = rowMajor;
rdcarray<ShaderVariable> varmembers;
if(isArray)
{
var.members.resize(elems);
for(uint32_t i = 0; i < elems; i++)
{
ShaderVariable &vr = var.members[i];
vr.name = StringFormat::Fmt("%s[%u]", basename.c_str(), i);
vr.rows = vr.columns = 0;
vr.type = VarType::Float;
vr.rowMajor = rowMajor;
StandardFillCBufferVariables(shader, invars[v].type.members, vr.members, data, dataOffset);
dataOffset += invars[v].type.descriptor.arrayByteStride;
vr.isStruct = true;
}
var.isStruct = false;
}
else
{
var.isStruct = true;
StandardFillCBufferVariables(shader, invars[v].type.members, var.members, data, dataOffset);
}
outvars.push_back(var);
continue;
}
size_t outIdx = outvars.size();
outvars.push_back({});
{
const VarType type = invars[v].type.descriptor.type;
outvars[outIdx].name = basename;
outvars[outIdx].rows = 1;
outvars[outIdx].type = type;
outvars[outIdx].isStruct = false;
outvars[outIdx].columns = cols;
outvars[outIdx].rowMajor = rowMajor;
ShaderVariable &var = outvars[outIdx];
if(!isArray)
{
outvars[outIdx].rows = rows;
StandardFillCBufferVariable(shader, invars[v].type.descriptor, dataOffset, data,
outvars[outIdx], matStride);
}
else
{
var.name = outvars[outIdx].name;
var.rows = 0;
var.columns = 0;
rdcarray<ShaderVariable> varmembers;
varmembers.resize(elems);
rdcstr base = outvars[outIdx].name;
for(uint32_t e = 0; e < elems; e++)
{
varmembers[e].name = StringFormat::Fmt("%s[%u]", base.c_str(), e);
varmembers[e].rows = rows;
varmembers[e].type = type;
varmembers[e].isStruct = false;
varmembers[e].columns = cols;
varmembers[e].rowMajor = rowMajor;
uint32_t rowDataOffset = dataOffset;
dataOffset += invars[v].type.descriptor.arrayByteStride;
StandardFillCBufferVariable(shader, invars[v].type.descriptor, rowDataOffset, data,
varmembers[e], matStride);
}
{
var.isStruct = false;
var.members = varmembers;
}
}
}
}
}
void StandardFillCBufferVariables(ResourceId shader, const rdcarray<ShaderConstant> &invars,
rdcarray<ShaderVariable> &outvars, const bytebuf &data)
{
// start with offset 0
StandardFillCBufferVariables(shader, invars, outvars, data, 0);
}
uint64_t CalcMeshOutputSize(uint64_t curSize, uint64_t requiredOutput)
{
if(curSize == 0)
curSize = 32 * 1024 * 1024;
// resize exponentially up to 256MB to avoid repeated resizes
while(curSize < requiredOutput && curSize < 0x10000000ULL)
curSize *= 2;
// after that, just align the required size up to 16MB and allocate that. Otherwise we can
// vastly-overallocate at large sizes.
if(curSize < requiredOutput)
curSize = AlignUp(requiredOutput, (uint64_t)0x1000000ULL);
return curSize;
}
FloatVector HighlightCache::InterpretVertex(const byte *data, uint32_t vert, const MeshDisplay &cfg,
const byte *end, bool useidx, bool &valid)
{
FloatVector ret(0.0f, 0.0f, 0.0f, 1.0f);
if(cfg.position.format.compType == CompType::UInt ||
cfg.position.format.compType == CompType::SInt || cfg.position.format.compCount == 4)
ret.w = 0.0f;
if(useidx && idxData)
{
if(vert >= (uint32_t)indices.size())
{
valid = false;
return ret;
}
vert = indices[vert];
if(SupportsRestart(cfg.position.topology) && cfg.position.topology != Topology::TriangleFan &&
cfg.position.allowRestart)
{
if((cfg.position.indexByteStride == 1 && vert == 0xff) ||
(cfg.position.indexByteStride == 2 && vert == 0xffff) ||
(cfg.position.indexByteStride == 4 && vert == 0xffffffff))
{
valid = false;
return ret;
}
}
}
return HighlightCache::InterpretVertex(data, vert, cfg.position.vertexByteStride,
cfg.position.format, end, valid);
}
FloatVector HighlightCache::InterpretVertex(const byte *data, uint32_t vert,
uint32_t vertexByteStride, const ResourceFormat &fmt,
const byte *end, bool &valid)
{
data += vert * vertexByteStride;
if(data + fmt.ElementSize() > end)
{
valid = false;
if(fmt.compType == CompType::UInt || fmt.compType == CompType::SInt || fmt.compCount == 4)
return FloatVector(0.0f, 0.0f, 0.0f, 0.0f);
return FloatVector(0.0f, 0.0f, 0.0f, 1.0f);
}
return DecodeFormattedComponents(fmt, data);
}
uint64_t inthash(uint64_t val, uint64_t seed)
{
return (seed << 5) + seed + val; /* hash * 33 + c */
}
uint64_t inthash(ResourceId id, uint64_t seed)
{
uint64_t val = 0;
memcpy(&val, &id, sizeof(val));
return (seed << 5) + seed + val; /* hash * 33 + c */
}
void HighlightCache::CacheHighlightingData(uint32_t eventId, const MeshDisplay &cfg)
{
rdcstr ident;
uint64_t newKey = 5381;
// hash all the properties of cfg that we use
newKey = inthash(eventId, newKey);
newKey = inthash(cfg.position.indexByteStride, newKey);
newKey = inthash(cfg.position.numIndices, newKey);
newKey = inthash((uint64_t)cfg.type, newKey);
newKey = inthash((uint64_t)cfg.position.baseVertex, newKey);
newKey = inthash((uint64_t)cfg.position.topology, newKey);
newKey = inthash(cfg.position.vertexByteOffset, newKey);
newKey = inthash(cfg.position.vertexByteStride, newKey);
newKey = inthash(cfg.position.indexResourceId, newKey);
newKey = inthash(cfg.position.vertexResourceId, newKey);
newKey = inthash((uint64_t)cfg.position.allowRestart, newKey);
newKey = inthash((uint64_t)cfg.position.restartIndex, newKey);
if(cacheKey != newKey)
{
cacheKey = newKey;
uint32_t bytesize = cfg.position.indexByteStride;
uint64_t maxIndex = cfg.position.numIndices - 1;
if(cfg.position.indexByteStride == 0 || cfg.type == MeshDataStage::GSOut)
{
indices.clear();
idxData = false;
}
else
{
idxData = true;
bytebuf idxdata;
if(cfg.position.indexResourceId != ResourceId())
driver->GetBufferData(cfg.position.indexResourceId, cfg.position.indexByteOffset,
cfg.position.numIndices * bytesize, idxdata);
uint8_t *idx8 = (uint8_t *)&idxdata[0];
uint16_t *idx16 = (uint16_t *)&idxdata[0];
uint32_t *idx32 = (uint32_t *)&idxdata[0];
uint32_t numIndices = RDCMIN(cfg.position.numIndices, uint32_t(idxdata.size() / bytesize));
indices.resize(numIndices);
if(bytesize == 1)
{
for(uint32_t i = 0; i < numIndices; i++)
{
indices[i] = uint32_t(idx8[i]);
maxIndex = RDCMAX(maxIndex, (uint64_t)indices[i]);
}
}
else if(bytesize == 2)
{
for(uint32_t i = 0; i < numIndices; i++)
{
indices[i] = uint32_t(idx16[i]);
maxIndex = RDCMAX(maxIndex, (uint64_t)indices[i]);
}
}
else if(bytesize == 4)
{
for(uint32_t i = 0; i < numIndices; i++)
{
indices[i] = idx32[i];
maxIndex = RDCMAX(maxIndex, (uint64_t)indices[i]);
}
}
uint32_t sub = uint32_t(-cfg.position.baseVertex);
uint32_t add = uint32_t(cfg.position.baseVertex);
if(cfg.position.baseVertex > 0)
maxIndex += add;
uint32_t primRestart = 0;
if(SupportsRestart(cfg.position.topology) && cfg.position.allowRestart)
{
if(cfg.position.indexByteStride == 1)
primRestart = 0xff;
else if(cfg.position.indexByteStride == 2)
primRestart = 0xffff;
else
primRestart = 0xffffffff;
}
for(uint32_t i = 0; cfg.position.baseVertex != 0 && i < numIndices; i++)
{
// don't modify primitive restart indices
if(primRestart && indices[i] == primRestart)
continue;
if(cfg.position.baseVertex < 0)
{
if(indices[i] < sub)
indices[i] = 0;
else
indices[i] -= sub;
}
else
{
indices[i] += add;
}
}
}
driver->GetBufferData(cfg.position.vertexResourceId, cfg.position.vertexByteOffset,
(maxIndex + 1) * cfg.position.vertexByteStride, vertexData);
// if it's a fan AND it uses primitive restart, decompose it into a triangle list because the
// restart changes the central vertex
if(cfg.position.topology == Topology::TriangleFan && cfg.position.allowRestart)
{
PatchTriangleFanRestartIndexBufer(indices, cfg.position.restartIndex);
}
}
}
bool HighlightCache::FetchHighlightPositions(const MeshDisplay &cfg, FloatVector &activeVertex,
rdcarray<FloatVector> &activePrim,
rdcarray<FloatVector> &adjacentPrimVertices,
rdcarray<FloatVector> &inactiveVertices)
{
bool valid = true;
byte *data = &vertexData[0];
byte *dataEnd = data + vertexData.size();
uint32_t idx = cfg.highlightVert;
Topology meshtopo = cfg.position.topology;
// if it's a fan AND it uses primitive restart, it was decomposed into a triangle list
if(meshtopo == Topology::TriangleFan && cfg.position.allowRestart)
{
meshtopo = Topology::TriangleList;
// due to triangle fan expansion the index we need to look up is adjusted
if(idx > 2)
idx = (idx - 1) * 3 - 1;
}
activeVertex = InterpretVertex(data, idx, cfg, dataEnd, true, valid);
uint32_t primRestart = 0;
if(SupportsRestart(meshtopo) && cfg.position.allowRestart)
{
if(cfg.position.indexByteStride == 1)
primRestart = 0xff;
else if(cfg.position.indexByteStride == 2)
primRestart = 0xffff;
else
primRestart = 0xffffffff;
}
// Reference for how primitive topologies are laid out:
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb205124(v=vs.85).aspx
// Section 19.1 of the Vulkan 1.0.48 spec
// Section 10.1 of the OpenGL 4.5 spec
if(meshtopo == Topology::LineList)
{
uint32_t v = uint32_t(idx / 2) * 2; // find first vert in primitive
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
}
else if(meshtopo == Topology::TriangleList)
{
uint32_t v = uint32_t(idx / 3) * 3; // find first vert in primitive
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 2, cfg, dataEnd, true, valid));
}
else if(meshtopo == Topology::LineList_Adj)
{
uint32_t v = uint32_t(idx / 4) * 4; // find first vert in primitive
FloatVector vs[] = {
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
};
adjacentPrimVertices.push_back(vs[0]);
adjacentPrimVertices.push_back(vs[1]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[3]);
activePrim.push_back(vs[1]);
activePrim.push_back(vs[2]);
}
else if(meshtopo == Topology::TriangleList_Adj)
{
uint32_t v = uint32_t(idx / 6) * 6; // find first vert in primitive
FloatVector vs[] = {
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 4, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 5, cfg, dataEnd, true, valid),
};
adjacentPrimVertices.push_back(vs[0]);
adjacentPrimVertices.push_back(vs[1]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[3]);
adjacentPrimVertices.push_back(vs[4]);
adjacentPrimVertices.push_back(vs[4]);
adjacentPrimVertices.push_back(vs[5]);
adjacentPrimVertices.push_back(vs[0]);
activePrim.push_back(vs[0]);
activePrim.push_back(vs[2]);
activePrim.push_back(vs[4]);
}
else if(meshtopo == Topology::LineStrip)
{
// find first vert in primitive. In strips a vert isn't
// in only one primitive, so we pick the first primitive
// it's in. This means the first N points are in the first
// primitive, and thereafter each point is in the next primitive
uint32_t v = RDCMAX(idx, 1U) - 1;
// skip past any primitive restart indices
if(idxData && primRestart)
{
while(v < (uint32_t)indices.size() && indices[v] == primRestart)
v++;
}
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
}
else if(meshtopo == Topology::TriangleFan)
{
// find first vert in primitive. In fans a vert isn't
// in only one primitive, so we pick the first primitive
// it's in. This means the first N points are in the first
// primitive, and thereafter each point is in the next primitive
uint32_t v = RDCMAX(idx, 2U) - 1;
// first vert in the whole fan
activePrim.push_back(InterpretVertex(data, 0, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
}
else if(meshtopo == Topology::TriangleStrip)
{
// find first vert in primitive. In strips a vert isn't
// in only one primitive, so we pick the first primitive
// it's in. This means the first N points are in the first
// primitive, and thereafter each point is in the next primitive
uint32_t v = RDCMAX(idx, 2U) - 2;
// skip past any primitive restart indices
if(idxData && primRestart)
{
while(v < (uint32_t)indices.size() &&
(indices[v + 0] == primRestart || indices[v + 1] == primRestart))
v++;
}
activePrim.push_back(InterpretVertex(data, v + 0, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 1, cfg, dataEnd, true, valid));
activePrim.push_back(InterpretVertex(data, v + 2, cfg, dataEnd, true, valid));
}
else if(meshtopo == Topology::LineStrip_Adj)
{
// find first vert in primitive. In strips a vert isn't
// in only one primitive, so we pick the first primitive
// it's in. This means the first N points are in the first
// primitive, and thereafter each point is in the next primitive
uint32_t v = RDCMAX(idx, 3U) - 3;
// skip past any primitive restart indices
if(idxData && primRestart)
{
while(v < (uint32_t)indices.size() &&
(indices[v + 0] == primRestart || indices[v + 1] == primRestart ||
indices[v + 2] == primRestart))
v++;
}
FloatVector vs[] = {
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
};
adjacentPrimVertices.push_back(vs[0]);
adjacentPrimVertices.push_back(vs[1]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[3]);
activePrim.push_back(vs[1]);
activePrim.push_back(vs[2]);
}
else if(meshtopo == Topology::TriangleStrip_Adj)
{
// Triangle strip with adjacency is the most complex topology, as
// we need to handle the ends separately where the pattern breaks.
uint32_t numidx = cfg.position.numIndices;
if(numidx < 6)
{
// not enough indices provided, bail to make sure logic below doesn't
// need to have tons of edge case detection
valid = false;
}
else if(idx <= 4 || numidx <= 7)
{
FloatVector vs[] = {
InterpretVertex(data, 0, cfg, dataEnd, true, valid),
InterpretVertex(data, 1, cfg, dataEnd, true, valid),
InterpretVertex(data, 2, cfg, dataEnd, true, valid),
InterpretVertex(data, 3, cfg, dataEnd, true, valid),
InterpretVertex(data, 4, cfg, dataEnd, true, valid),
// note this one isn't used as it's adjacency for the next triangle
InterpretVertex(data, 5, cfg, dataEnd, true, valid),
// min() with number of indices in case this is a tiny strip
// that is basically just a list
InterpretVertex(data, RDCMIN(6U, numidx - 1), cfg, dataEnd, true, valid),
};
// these are the triangles on the far left of the MSDN diagram above
adjacentPrimVertices.push_back(vs[0]);
adjacentPrimVertices.push_back(vs[1]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[4]);
adjacentPrimVertices.push_back(vs[3]);
adjacentPrimVertices.push_back(vs[0]);
adjacentPrimVertices.push_back(vs[4]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[6]);
activePrim.push_back(vs[0]);
activePrim.push_back(vs[2]);
activePrim.push_back(vs[4]);
}
else if(idx > numidx - 4)
{
// in diagram, numidx == 14
FloatVector vs[] = {
/*[0]=*/InterpretVertex(data, numidx - 8, cfg, dataEnd, true, valid), // 6 in diagram
// as above, unused since this is adjacency for 2-previous triangle
/*[1]=*/InterpretVertex(data, numidx - 7, cfg, dataEnd, true, valid), // 7 in diagram
/*[2]=*/InterpretVertex(data, numidx - 6, cfg, dataEnd, true, valid), // 8 in diagram
// as above, unused since this is adjacency for previous triangle
/*[3]=*/InterpretVertex(data, numidx - 5, cfg, dataEnd, true, valid), // 9 in diagram
/*[4]=*/InterpretVertex(data, numidx - 4, cfg, dataEnd, true,
valid), // 10 in diagram
/*[5]=*/InterpretVertex(data, numidx - 3, cfg, dataEnd, true,
valid), // 11 in diagram
/*[6]=*/InterpretVertex(data, numidx - 2, cfg, dataEnd, true,
valid), // 12 in diagram
/*[7]=*/InterpretVertex(data, numidx - 1, cfg, dataEnd, true,
valid), // 13 in diagram
};
// these are the triangles on the far right of the MSDN diagram above
adjacentPrimVertices.push_back(vs[2]); // 8 in diagram
adjacentPrimVertices.push_back(vs[0]); // 6 in diagram
adjacentPrimVertices.push_back(vs[4]); // 10 in diagram
adjacentPrimVertices.push_back(vs[4]); // 10 in diagram
adjacentPrimVertices.push_back(vs[7]); // 13 in diagram
adjacentPrimVertices.push_back(vs[6]); // 12 in diagram
adjacentPrimVertices.push_back(vs[6]); // 12 in diagram
adjacentPrimVertices.push_back(vs[5]); // 11 in diagram
adjacentPrimVertices.push_back(vs[2]); // 8 in diagram
activePrim.push_back(vs[2]); // 8 in diagram
activePrim.push_back(vs[4]); // 10 in diagram
activePrim.push_back(vs[6]); // 12 in diagram
}
else
{
// we're in the middle somewhere. Each primitive has two vertices for it
// so our step rate is 2. The first 'middle' primitive starts at indices 5&6
// and uses indices all the way back to 0
uint32_t v = RDCMAX(((idx + 1) / 2) * 2, 6U) - 6;
// skip past any primitive restart indices
if(idxData && primRestart)
{
while(v < (uint32_t)indices.size() &&
(indices[v + 0] == primRestart || indices[v + 1] == primRestart ||
indices[v + 2] == primRestart || indices[v + 3] == primRestart ||
indices[v + 4] == primRestart || indices[v + 5] == primRestart))
v++;
}
// these correspond to the indices in the MSDN diagram, with {2,4,6} as the
// main triangle
FloatVector vs[] = {
InterpretVertex(data, v + 0, cfg, dataEnd, true, valid),
// this one is adjacency for 2-previous triangle
InterpretVertex(data, v + 1, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 2, cfg, dataEnd, true, valid),
// this one is adjacency for previous triangle
InterpretVertex(data, v + 3, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 4, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 5, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 6, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 7, cfg, dataEnd, true, valid),
InterpretVertex(data, v + 8, cfg, dataEnd, true, valid),
};
// these are the triangles around {2,4,6} in the MSDN diagram above
adjacentPrimVertices.push_back(vs[0]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[4]);
adjacentPrimVertices.push_back(vs[2]);
adjacentPrimVertices.push_back(vs[5]);
adjacentPrimVertices.push_back(vs[6]);
adjacentPrimVertices.push_back(vs[6]);
adjacentPrimVertices.push_back(vs[8]);
adjacentPrimVertices.push_back(vs[4]);
activePrim.push_back(vs[2]);
activePrim.push_back(vs[4]);
activePrim.push_back(vs[6]);
}
}
else if(meshtopo >= Topology::PatchList)
{
uint32_t dim = PatchList_Count(meshtopo);
uint32_t v0 = uint32_t(idx / dim) * dim;
for(uint32_t v = v0; v < v0 + dim; v++)
{
if(v != idx && valid)
inactiveVertices.push_back(InterpretVertex(data, v, cfg, dataEnd, true, valid));
}
}
else // if(meshtopo == Topology::PointList) point list, or unknown/unhandled type
{
// no adjacency, inactive verts or active primitive
}
return valid;
}
// colour ramp from http://www.ncl.ucar.edu/Document/Graphics/ColorTables/GMT_wysiwyg.shtml
const Vec4f colorRamp[22] = {
Vec4f(0.000000f, 0.000000f, 0.000000f, 0.0f), Vec4f(0.250980f, 0.000000f, 0.250980f, 1.0f),
Vec4f(0.250980f, 0.000000f, 0.752941f, 1.0f), Vec4f(0.000000f, 0.250980f, 1.000000f, 1.0f),
Vec4f(0.000000f, 0.501961f, 1.000000f, 1.0f), Vec4f(0.000000f, 0.627451f, 1.000000f, 1.0f),
Vec4f(0.250980f, 0.752941f, 1.000000f, 1.0f), Vec4f(0.250980f, 0.878431f, 1.000000f, 1.0f),
Vec4f(0.250980f, 1.000000f, 1.000000f, 1.0f), Vec4f(0.250980f, 1.000000f, 0.752941f, 1.0f),
Vec4f(0.250980f, 1.000000f, 0.250980f, 1.0f), Vec4f(0.501961f, 1.000000f, 0.250980f, 1.0f),
Vec4f(0.752941f, 1.000000f, 0.250980f, 1.0f), Vec4f(1.000000f, 1.000000f, 0.250980f, 1.0f),
Vec4f(1.000000f, 0.878431f, 0.250980f, 1.0f), Vec4f(1.000000f, 0.627451f, 0.250980f, 1.0f),
Vec4f(1.000000f, 0.376471f, 0.250980f, 1.0f), Vec4f(1.000000f, 0.125490f, 0.250980f, 1.0f),
Vec4f(1.000000f, 0.376471f, 0.752941f, 1.0f), Vec4f(1.000000f, 0.627451f, 1.000000f, 1.0f),
Vec4f(1.000000f, 0.878431f, 1.000000f, 1.0f), Vec4f(1.000000f, 1.000000f, 1.000000f, 1.0f),
};
bytebuf GetDiscardPattern(DiscardType type, const ResourceFormat &fmt, uint32_t rowPitch, bool invert)
{
static const rdcliteral patterns[] = {
// DiscardType::RenderPassLoad
"..#.....##...##..##....##....##..#..#.####..###..##..###..####.."
"..#....#..#.#..#.#.#...#.#..#..#.##.#..#...#....#..#.#..#.#....."
"..#....#..#.#..#.#..#..#..#.#..#.##.#..#...#....#..#.#..#.###..."
"..#....#..#.####.#..#..#..#.#..#.#.##..#...#....####.###..#....."
"..#....#..#.#..#.#.#...#.#..#..#.#.##..#...#....#..#.#..#.#....."
"..####..##..#..#.##....##....##..#..#..#....###.#..#.#..#.####.."
"................................................................"
"................................................................"_lit,
// DiscardType::RenderPassStore
"...###.####..##..###...##....##..#..#.####..###..##..###..####.."
"..#.....#...#..#.#..#..#.#..#..#.##.#..#...#....#..#.#..#.#....."
"...#....#...#..#.#..#..#..#.#..#.##.#..#...#....#..#.#..#.###..."
"....#...#...#..#.###...#..#.#..#.#.##..#...#....####.###..#....."
".....#..#...#..#.#..#..#.#..#..#.#.##..#...#....#..#.#..#.#....."
"..###...#....##..#..#..##....##..#..#..#....###.#..#.#..#.####.."
"................................................................"
"................................................................"_lit,
// DiscardType::UndefinedTransition
"..#..#.#..#.##...####.####.####.#..#.####.##....####.#..#..###.."
"..#..#.##.#.#.#..#....#.....#...##.#.#....#.#....#...####.#....."
"..#..#.##.#.#..#.###..###...#...##.#.###..#..#...#...##.#.#....."
"..#..#.#.##.#..#.#....#.....#...#.##.#....#..#...#...#..#.#.##.."
"..#..#.#.##.#.#..#....#.....#...#.##.#....#.#....#...#..#.#..#.."
"...##..#..#.##...####.#....####.#..#.####.##....####.#..#..##..."
"................................................................"
"................................................................"_lit,
// DiscardType::DiscardCall
"..##...####..###..###...#...###..##...####.##...####.#..#..###.."
"..#.#...#...#....#.....#.#..#..#.#.#..#....#.#...#...####.#....."
"..#..#..#....#...#.....#.#..#..#.#..#.###..#..#..#...##.#.#....."
"..#..#..#.....#..#....#####.###..#..#.#....#..#..#...#..#.#.##.."
"..#.#...#......#.#....#...#.#..#.#.#..#....#.#...#...#..#.#..#.."
"..##...####.###...###.#...#.#..#.##...####.##...####.#..#..##..."
"................................................................"
"................................................................"_lit,
// DiscardType::InvalidateCall
"...####.#..#.#...#...#...#....####.##.....#...#####.####.##....."
"....#...##.#.#...#..#.#..#.....#...#.#...#.#....#...#....#.#...."
"....#...##.#.#...#..#.#..#.....#...#..#..#.#....#...###..#..#..."
"....#...#.##..#.#..#####.#.....#...#..#.#####...#...#....#..#..."
"....#...#.##..#.#..#...#.#.....#...#.#..#...#...#...#....#.#...."
"...####.#..#...#...#...#.####.####.##...#...#...#...####.##....."
"................................................................"
"................................................................"_lit,
};
const rdcliteral &pattern = patterns[(int)type];
RDCASSERT(pattern.length() == DiscardPatternWidth * DiscardPatternHeight);
bytebuf ret;
if(fmt.type == ResourceFormatType::Regular || fmt.type == ResourceFormatType::A8 ||
fmt.type == ResourceFormatType::S8)
{
byte black[8] = {};
byte white[8] = {};
if(fmt.compType == CompType::Float)
{
if(fmt.compByteWidth == 8)
{
double b = 0.0;
double w = 1000.0;
memcpy(black, &b, sizeof(b));
memcpy(white, &w, sizeof(w));
}
else if(fmt.compByteWidth == 4)
{
float b = 0.0f;
float w = 1000.0f;
memcpy(black, &b, sizeof(b));
memcpy(white, &w, sizeof(w));
}
else
{
uint16_t b = ConvertToHalf(0.0f);
uint16_t w = ConvertToHalf(1000.0f);
memcpy(black, &b, sizeof(b));
memcpy(white, &w, sizeof(w));
}
}
else if(fmt.compType == CompType::Depth)
{
if(fmt.compByteWidth == 4)
{
float b = 0.0f;
float w = 1.0f;
memcpy(black, &b, sizeof(b));
memcpy(white, &w, sizeof(w));
}
else
{
// other depth formats are normalised
memset(black, 0, sizeof(black));
memset(white, 0xff, sizeof(white));
}
}
else if(fmt.compType == CompType::UInt || fmt.compType == CompType::SInt)
{
// ints we use 0 and 127 so it's the same for every signed type and byte width
white[0] = 127;
}
else
{
// all other types are normalised, so we just set white to 0xff
memset(black, 0, sizeof(black));
memset(white, 0xff, sizeof(white));
}
uint32_t tightPitch = DiscardPatternWidth * fmt.compByteWidth * fmt.compCount;
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
byte *out = ret.data();
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
for(uint8_t i = 0; i < fmt.compCount; i++)
{
if(c == '#')
memcpy(out, white, fmt.compByteWidth);
else
memcpy(out, black, fmt.compByteWidth);
out += fmt.compByteWidth;
}
}
out += (rowPitch - tightPitch);
}
}
else if(fmt.type == ResourceFormatType::R10G10B10A2)
{
uint32_t tightPitch = DiscardPatternWidth * sizeof(uint32_t);
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
uint32_t *out = (uint32_t *)ret.data();
uint32_t minVal = 0;
uint32_t maxVal = 0xffffffff;
if(fmt.compType == CompType::UInt)
maxVal = (127u << 0) | (127u << 10) | (127u << 20) | (3u << 30);
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
*(out++) = (c == '#') ? maxVal : minVal;
}
out += (rowPitch - tightPitch);
}
}
else if(fmt.type == ResourceFormatType::R5G6B5 || fmt.type == ResourceFormatType::R5G5B5A1 ||
fmt.type == ResourceFormatType::R4G4B4A4)
{
uint32_t tightPitch = DiscardPatternWidth * sizeof(uint16_t);
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
uint16_t *out = (uint16_t *)ret.data();
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
*(out++) = (c == '#') ? 0xffff : 0x0000;
}
out += (rowPitch - tightPitch);
}
}
else if(fmt.type == ResourceFormatType::R4G4)
{
uint32_t tightPitch = DiscardPatternWidth * sizeof(uint8_t);
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
byte *out = ret.data();
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
*(out++) = (c == '#') ? 0xff : 0x00;
}
out += (rowPitch - tightPitch);
}
}
else if(fmt.type == ResourceFormatType::R11G11B10)
{
const uint32_t black = ConvertToR11G11B10(Vec3f(0.0f, 0.0f, 0.0f));
const uint32_t white = ConvertToR11G11B10(Vec3f(1000.0f, 1000.0f, 1000.0f));
uint32_t tightPitch = DiscardPatternWidth * sizeof(uint32_t);
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
uint32_t *out = (uint32_t *)ret.data();
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
*(out++) = (c == '#') ? white : black;
}
out += (rowPitch - tightPitch);
}
}
else if(fmt.type == ResourceFormatType::R9G9B9E5)
{
const uint32_t black = ConvertToR9G9B9E5(Vec3f(0.0f, 0.0f, 0.0f));
const uint32_t white = ConvertToR9G9B9E5(Vec3f(1000.0f, 1000.0f, 1000.0f));
uint32_t tightPitch = DiscardPatternWidth * sizeof(uint32_t);
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
uint32_t *out = (uint32_t *)ret.data();
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
*(out++) = (c == '#') ? white : black;
}
out += (rowPitch - tightPitch);
}
}
else if(fmt.type == ResourceFormatType::D16S8 || fmt.type == ResourceFormatType::D24S8 ||
fmt.type == ResourceFormatType::D32S8)
{
uint32_t white = 0xffffffff;
uint32_t black = 0;
uint32_t depthStride = 0;
if(fmt.type == ResourceFormatType::D16S8)
{
depthStride = 2;
}
else if(fmt.type == ResourceFormatType::D24S8)
{
depthStride = 4;
}
else if(fmt.type == ResourceFormatType::D32S8)
{
depthStride = 4;
float maxDepth = 1.0f;
memcpy(&white, &maxDepth, sizeof(float));
}
uint32_t tightPitch = DiscardPatternWidth * depthStride;
uint32_t tightStencilPitch = DiscardPatternWidth * sizeof(byte);
rowPitch = RDCMAX(rowPitch, RDCMAX(tightPitch, tightStencilPitch));
ret.resize(rowPitch * DiscardPatternHeight * 2);
byte *depthOut = ret.data();
byte *stencilOut = depthOut + rowPitch * DiscardPatternHeight;
for(int yi = 0; yi < (int)DiscardPatternHeight; yi++)
{
int y = invert ? DiscardPatternHeight - 1 - yi : yi;
for(int x = 0; x < (int)DiscardPatternWidth; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
if(c == '#')
{
memcpy(depthOut, &white, depthStride);
*(stencilOut++) = 0xff;
}
else
{
memcpy(depthOut, &black, depthStride);
*(stencilOut++) = 0x00;
}
depthOut += depthStride;
}
depthOut += (rowPitch - tightPitch);
stencilOut += (rowPitch - tightStencilPitch);
}
}
else if(fmt.type == ResourceFormatType::BC1 || fmt.type == ResourceFormatType::BC2 ||
fmt.type == ResourceFormatType::BC3 || fmt.type == ResourceFormatType::BC4 ||
fmt.type == ResourceFormatType::BC5 || fmt.type == ResourceFormatType::BC6 ||
fmt.type == ResourceFormatType::BC7)
{
#if ENABLED(RDOC_ANDROID)
RDCERR("Format %s not supported on android", fmt.Name().c_str());
#else
const uint16_t whalf = ConvertToHalf(1000.0f);
byte block[16];
uint32_t blockSize =
fmt.type == ResourceFormatType::BC1 || fmt.type == ResourceFormatType::BC4 ? 8 : 16;
uint32_t tightPitch = (DiscardPatternWidth / 4) * blockSize;
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.reserve(rowPitch * (DiscardPatternHeight / 4));
bytebuf inblock;
void *bc6opts = NULL;
CreateOptionsBC6(&bc6opts);
SetQualityBC6(bc6opts, 0.1f);
for(uint32_t yi = 0; yi < DiscardPatternHeight; yi += 4)
{
uint32_t baseY = invert ? DiscardPatternHeight - 1 - yi : yi;
for(uint32_t baseX = 0; baseX < DiscardPatternWidth; baseX += 4)
{
inblock.clear();
// inblock is 4x4 RGBA8_UNORM
if(fmt.type == ResourceFormatType::BC1 || fmt.type == ResourceFormatType::BC2 ||
fmt.type == ResourceFormatType::BC3 || fmt.type == ResourceFormatType::BC7)
{
for(uint32_t y = baseY; y < baseY + 4; invert ? y-- : y++)
{
for(uint32_t x = baseX; x < baseX + 4; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
inblock.push_back(c == '#' ? 0xff : 0x00);
inblock.push_back(c == '#' ? 0xff : 0x00);
inblock.push_back(c == '#' ? 0xff : 0x00);
inblock.push_back(c == '#' ? 0xff : 0x00);
}
}
}
// inblock is 4x4 R8_UNORM
else if(fmt.type == ResourceFormatType::BC4 || fmt.type == ResourceFormatType::BC5)
{
for(uint32_t y = baseY; y < baseY + 4; invert ? y-- : y++)
{
for(uint32_t x = baseX; x < baseX + 4; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
inblock.push_back(c == '#' ? 0xff : 0x00);
}
}
}
// inblock is 4x4 RGB16_FLOAT
else if(fmt.type == ResourceFormatType::BC6)
{
for(uint32_t y = baseY; y < baseY + 4; invert ? y-- : y++)
{
for(uint32_t x = baseX; x < baseX + 4; x++)
{
char c = pattern.c_str()[y * DiscardPatternWidth + x];
inblock.push_back(c == '#' ? (whalf & 0xff) : 0x00);
inblock.push_back(c == '#' ? ((whalf >> 8) & 0xff) : 0x00);
inblock.push_back(c == '#' ? (whalf & 0xff) : 0x00);
inblock.push_back(c == '#' ? ((whalf >> 8) & 0xff) : 0x00);
inblock.push_back(c == '#' ? (whalf & 0xff) : 0x00);
inblock.push_back(c == '#' ? ((whalf >> 8) & 0xff) : 0x00);
}
}
}
if(fmt.type == ResourceFormatType::BC1)
CompressBlockBC1(inblock.data(), 4 * sizeof(uint32_t), block, NULL);
else if(fmt.type == ResourceFormatType::BC2)
CompressBlockBC2(inblock.data(), 4 * sizeof(uint32_t), block, NULL);
else if(fmt.type == ResourceFormatType::BC3)
CompressBlockBC3(inblock.data(), 4 * sizeof(uint32_t), block, NULL);
else if(fmt.type == ResourceFormatType::BC4)
CompressBlockBC4(inblock.data(), 4, block, NULL);
else if(fmt.type == ResourceFormatType::BC5)
CompressBlockBC5(inblock.data(), 4, inblock.data(), 4, block, NULL);
else if(fmt.type == ResourceFormatType::BC6)
CompressBlockBC6((uint16_t *)inblock.data(), 4 * 3, block, bc6opts);
else if(fmt.type == ResourceFormatType::BC7)
CompressBlockBC7(inblock.data(), 4 * sizeof(uint32_t), block, NULL);
ret.append(block, blockSize);
}
ret.resize(ret.size() + (rowPitch - tightPitch));
}
DestroyOptionsBC6(bc6opts);
#endif
}
else if(fmt.type == ResourceFormatType::ETC2 || fmt.type == ResourceFormatType::EAC ||
fmt.type == ResourceFormatType::ASTC || fmt.type == ResourceFormatType::PVRTC ||
fmt.type == ResourceFormatType::YUV8 || fmt.type == ResourceFormatType::YUV10 ||
fmt.type == ResourceFormatType::YUV12 || fmt.type == ResourceFormatType::YUV16)
{
RDCERR("Format %s not supported for proper discard pattern", fmt.Name().c_str());
}
else
{
RDCERR("Unhandled format %s needing discard pattern", fmt.Name().c_str());
}
// if we didn't get a proper pattern, try at least to do some kind of checkerboard (not knowing if
// this will align with the format or not)
if(ret.empty())
{
uint32_t tightPitch = DiscardPatternWidth * 16;
rowPitch = RDCMAX(rowPitch, tightPitch);
ret.resize(rowPitch * DiscardPatternHeight);
byte *out = ret.data();
int val = 0;
for(uint32_t y = 0; y < DiscardPatternHeight; y++)
{
byte *rowout = out;
for(uint32_t i = 0; i < DiscardPatternWidth * DiscardPatternHeight; i++)
{
memset(rowout, val, 16);
rowout += 16;
// toggle between memset(0) and memset(0xff)
if(val)
val = 0;
else
val = 0xff;
}
out += rowPitch;
}
}
return ret;
}