Files
renderdoc/renderdoc/replay/replay_renderer.cpp
T
baldurk 3695d7de6d Save EXR images in ABGR channel order, for compatibility with viewers
* It seems many viewers don't look at the order specified, and just
  assume that the channels are in ABGR order. Changing it won't affect
  any loaders that do respect that order.
2016-09-10 16:26:19 +02:00

1888 lines
54 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2015-2016 Baldur Karlsson
* Copyright (c) 2014 Crytek
*
* 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_renderer.h"
#include <string.h>
#include <time.h>
#include "common/dds_readwrite.h"
#include "jpeg-compressor/jpgd.h"
#include "jpeg-compressor/jpge.h"
#include "maths/formatpacking.h"
#include "os/os_specific.h"
#include "serialise/serialiser.h"
#include "serialise/string_utils.h"
#include "stb/stb_image.h"
#include "stb/stb_image_write.h"
#include "tinyexr/tinyexr.h"
float ConvertComponent(ResourceFormat fmt, byte *data)
{
if(fmt.compByteWidth == 4)
{
uint32_t *u32 = (uint32_t *)data;
int32_t *i32 = (int32_t *)data;
if(fmt.compType == eCompType_Float)
{
return *(float *)u32;
}
else if(fmt.compType == eCompType_UInt || fmt.compType == eCompType_UScaled)
{
return float(*u32);
}
else if(fmt.compType == eCompType_SInt || fmt.compType == eCompType_SScaled)
{
return float(*i32);
}
}
else if(fmt.compByteWidth == 2)
{
uint16_t *u16 = (uint16_t *)data;
int16_t *i16 = (int16_t *)data;
if(fmt.compType == eCompType_Float)
{
return ConvertFromHalf(*u16);
}
else if(fmt.compType == eCompType_UInt || fmt.compType == eCompType_UScaled)
{
return float(*u16);
}
else if(fmt.compType == eCompType_SInt || fmt.compType == eCompType_SScaled)
{
return float(*i16);
}
else if(fmt.compType == eCompType_UNorm)
{
return float(*u16) / 65535.0f;
}
else if(fmt.compType == eCompType_SNorm)
{
float f = -1.0f;
if(*i16 == -32768)
f = -1.0f;
else
f = ((float)*i16) / 32767.0f;
return f;
}
}
else if(fmt.compByteWidth == 1)
{
uint8_t *u8 = (uint8_t *)data;
int8_t *i8 = (int8_t *)data;
if(fmt.compType == eCompType_UInt || fmt.compType == eCompType_UScaled)
{
return float(*u8);
}
else if(fmt.compType == eCompType_SInt || fmt.compType == eCompType_SScaled)
{
return float(*i8);
}
else if(fmt.compType == eCompType_UNorm)
{
if(fmt.srgbCorrected)
return SRGB8_lookuptable[*u8];
else
return float(*u8) / 255.0f;
}
else if(fmt.compType == eCompType_SNorm)
{
float f = -1.0f;
if(*i8 == -128)
f = -1.0f;
else
f = ((float)*i8) / 127.0f;
return f;
}
}
RDCERR("Unexpected format to convert from");
return 0.0f;
}
static void fileWriteFunc(void *context, void *data, int size)
{
FileIO::fwrite(data, 1, size, (FILE *)context);
}
ReplayRenderer::ReplayRenderer()
{
m_pDevice = NULL;
m_EventID = 100000;
}
ReplayRenderer::~ReplayRenderer()
{
for(size_t i = 0; i < m_Outputs.size(); i++)
SAFE_DELETE(m_Outputs[i]);
m_Outputs.clear();
for(auto it = m_CustomShaders.begin(); it != m_CustomShaders.end(); ++it)
m_pDevice->FreeCustomShader(*it);
m_CustomShaders.clear();
for(auto it = m_TargetResources.begin(); it != m_TargetResources.end(); ++it)
m_pDevice->FreeTargetResource(*it);
m_TargetResources.clear();
if(m_pDevice)
m_pDevice->Shutdown();
m_pDevice = NULL;
}
bool ReplayRenderer::SetFrameEvent(uint32_t eventID, bool force)
{
if(eventID != m_EventID || force)
{
m_EventID = eventID;
m_pDevice->ReplayLog(eventID, eReplay_WithoutDraw);
FetchPipelineState();
for(size_t i = 0; i < m_Outputs.size(); i++)
m_Outputs[i]->SetFrameEvent(eventID);
m_pDevice->ReplayLog(eventID, eReplay_OnlyDraw);
}
return true;
}
bool ReplayRenderer::GetD3D11PipelineState(D3D11PipelineState *state)
{
if(state)
{
*state = m_D3D11PipelineState;
return true;
}
return false;
}
bool ReplayRenderer::GetGLPipelineState(GLPipelineState *state)
{
if(state)
{
*state = m_GLPipelineState;
return true;
}
return false;
}
bool ReplayRenderer::GetVulkanPipelineState(VulkanPipelineState *state)
{
if(state)
{
*state = m_VulkanPipelineState;
return true;
}
return false;
}
bool ReplayRenderer::GetFrameInfo(FetchFrameInfo *info)
{
if(info == NULL)
return false;
*info = m_FrameRecord.frameInfo;
return true;
}
FetchDrawcall *ReplayRenderer::GetDrawcallByEID(uint32_t eventID)
{
if(eventID >= m_Drawcalls.size())
return NULL;
return m_Drawcalls[eventID];
}
bool ReplayRenderer::GetDrawcalls(rdctype::array<FetchDrawcall> *draws)
{
if(draws == NULL)
return false;
*draws = m_FrameRecord.m_DrawCallList;
return true;
}
bool ReplayRenderer::FetchCounters(uint32_t *counters, uint32_t numCounters,
rdctype::array<CounterResult> *results)
{
if(results == NULL)
return false;
vector<uint32_t> counterArray;
counterArray.reserve(numCounters);
for(uint32_t i = 0; i < numCounters; i++)
counterArray.push_back(counters[i]);
*results = m_pDevice->FetchCounters(counterArray);
return true;
}
bool ReplayRenderer::EnumerateCounters(rdctype::array<uint32_t> *counters)
{
if(counters == NULL)
return false;
*counters = m_pDevice->EnumerateCounters();
return true;
}
bool ReplayRenderer::DescribeCounter(uint32_t counterID, CounterDescription *desc)
{
if(desc == NULL)
return false;
m_pDevice->DescribeCounter(counterID, *desc);
return true;
}
bool ReplayRenderer::GetBuffers(rdctype::array<FetchBuffer> *out)
{
if(m_Buffers.empty())
{
vector<ResourceId> ids = m_pDevice->GetBuffers();
m_Buffers.resize(ids.size());
for(size_t i = 0; i < ids.size(); i++)
m_Buffers[i] = m_pDevice->GetBuffer(ids[i]);
}
if(out)
{
*out = m_Buffers;
return true;
}
return false;
}
bool ReplayRenderer::GetTextures(rdctype::array<FetchTexture> *out)
{
if(m_Textures.empty())
{
vector<ResourceId> ids = m_pDevice->GetTextures();
m_Textures.resize(ids.size());
for(size_t i = 0; i < ids.size(); i++)
m_Textures[i] = m_pDevice->GetTexture(ids[i]);
}
if(out)
{
*out = m_Textures;
return true;
}
return false;
}
bool ReplayRenderer::GetResolve(uint64_t *callstack, uint32_t callstackLen,
rdctype::array<rdctype::str> *arr)
{
if(arr == NULL || callstack == NULL || callstackLen == 0)
return false;
Callstack::StackResolver *resolv = m_pDevice->GetCallstackResolver();
if(resolv == NULL)
{
create_array_uninit(*arr, 1);
arr->elems[0] = "";
return true;
}
create_array_uninit(*arr, callstackLen);
for(size_t i = 0; i < callstackLen; i++)
{
Callstack::AddressDetails info = resolv->GetAddr(callstack[i]);
arr->elems[i] = info.formattedString();
}
return true;
}
bool ReplayRenderer::GetDebugMessages(rdctype::array<DebugMessage> *msgs)
{
if(msgs)
{
*msgs = m_pDevice->GetDebugMessages();
return true;
}
return false;
}
bool ReplayRenderer::GetUsage(ResourceId id, rdctype::array<EventUsage> *usage)
{
if(usage)
{
*usage = m_pDevice->GetUsage(m_pDevice->GetLiveID(id));
return true;
}
return false;
}
bool ReplayRenderer::GetPostVSData(uint32_t instID, MeshDataStage stage, MeshFormat *data)
{
if(data == NULL)
return false;
FetchDrawcall *draw = GetDrawcallByEID(m_EventID);
MeshFormat ret;
RDCEraseEl(ret);
if(draw == NULL || (draw->flags & eDraw_Drawcall) == 0)
return false;
instID = RDCMIN(instID, draw->numInstances - 1);
*data = m_pDevice->GetPostVSBuffers(draw->eventID, instID, stage);
return true;
}
bool ReplayRenderer::GetMinMax(ResourceId tex, uint32_t sliceFace, uint32_t mip, uint32_t sample,
FormatComponentType typeHint, PixelValue *minval, PixelValue *maxval)
{
PixelValue *a = minval;
PixelValue *b = maxval;
PixelValue dummy;
if(a == NULL)
a = &dummy;
if(b == NULL)
b = &dummy;
return m_pDevice->GetMinMax(m_pDevice->GetLiveID(tex), sliceFace, mip, sample, typeHint,
&a->value_f[0], &b->value_f[0]);
}
bool ReplayRenderer::GetHistogram(ResourceId tex, uint32_t sliceFace, uint32_t mip, uint32_t sample,
FormatComponentType typeHint, float minval, float maxval,
bool channels[4], rdctype::array<uint32_t> *histogram)
{
if(histogram == NULL)
return false;
vector<uint32_t> hist;
bool ret = m_pDevice->GetHistogram(m_pDevice->GetLiveID(tex), sliceFace, mip, sample, typeHint,
minval, maxval, channels, hist);
if(ret)
*histogram = hist;
return ret;
}
bool ReplayRenderer::GetBufferData(ResourceId buff, uint64_t offset, uint64_t len,
rdctype::array<byte> *data)
{
if(data == NULL || buff == ResourceId())
return false;
ResourceId liveId = m_pDevice->GetLiveID(buff);
if(liveId == ResourceId())
{
RDCERR("Couldn't get Live ID for %llu getting buffer data", buff);
return false;
}
vector<byte> retData;
m_pDevice->GetBufferData(liveId, offset, len, retData);
create_array_init(*data, retData.size(), !retData.empty() ? &retData[0] : NULL);
return true;
}
bool ReplayRenderer::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip,
rdctype::array<byte> *data)
{
if(data == NULL)
return false;
ResourceId liveId = m_pDevice->GetLiveID(tex);
if(liveId == ResourceId())
{
RDCERR("Couldn't get Live ID for %llu getting texture data", tex);
return false;
}
size_t sz = 0;
byte *bytes = m_pDevice->GetTextureData(liveId, arrayIdx, mip, false, eCompType_None, false,
false, 0.0f, 0.0f, sz);
if(sz == 0 || bytes == NULL)
create_array_uninit(*data, 0);
else
create_array_init(*data, sz, bytes);
SAFE_DELETE_ARRAY(bytes);
return true;
}
bool ReplayRenderer::SaveTexture(const TextureSave &saveData, const char *path)
{
TextureSave sd = saveData; // mutable copy
ResourceId liveid = m_pDevice->GetLiveID(sd.id);
FetchTexture td = m_pDevice->GetTexture(liveid);
bool success = false;
// clamp sample/mip/slice indices
if(td.msSamp == 1)
{
sd.sample.sampleIndex = 0;
sd.sample.mapToArray = false;
}
else
{
if(sd.sample.sampleIndex != ~0U)
sd.sample.sampleIndex = RDCCLAMP(sd.sample.sampleIndex, 0U, td.msSamp);
}
// don't support cube cruciform for non cubemaps, or
// cubemap arrays
if(!td.cubemap || td.arraysize != 6 || td.msSamp != 1)
sd.slice.cubeCruciform = false;
if(sd.mip != -1)
sd.mip = RDCCLAMP(sd.mip, 0, (int32_t)td.mips);
if(sd.slice.sliceIndex != -1)
sd.slice.sliceIndex = RDCCLAMP(sd.slice.sliceIndex, 0, int32_t(td.arraysize * td.depth));
if(td.arraysize * td.depth * td.msSamp == 1)
{
sd.slice.sliceIndex = 0;
sd.slice.slicesAsGrid = false;
}
// can't extract a channel that's not in the source texture
if(sd.channelExtract >= 0 && (uint32_t)sd.channelExtract >= td.format.compCount)
sd.channelExtract = -1;
sd.slice.sliceGridWidth = RDCMAX(sd.slice.sliceGridWidth, 1);
// store sample count so we know how many 'slices' is one real slice
// multisampled textures cannot have mips, subresource layout is same as would be for mips:
// [slice0 sample0], [slice0 sample1], [slice1 sample0], [slice1 sample1]
uint32_t sampleCount = td.msSamp;
bool multisampled = td.msSamp > 1;
bool resolveSamples = (sd.sample.sampleIndex == ~0U);
if(resolveSamples)
{
td.msSamp = 1;
sd.sample.mapToArray = false;
sd.sample.sampleIndex = 0;
}
// treat any multisampled texture as if it were an array
// of <sample count> dimension (on top of potential existing array
// dimension). GetTextureData() uses the same convention.
if(td.msSamp > 1)
{
td.arraysize *= td.msSamp;
td.msSamp = 1;
}
if(sd.destType != eFileType_DDS && sd.sample.mapToArray && !sd.slice.slicesAsGrid &&
sd.slice.sliceIndex == -1)
{
sd.sample.mapToArray = false;
sd.sample.sampleIndex = 0;
}
// only DDS supports writing multiple mips, fall back to mip 0 if 'all mips' was specified
if(sd.destType != eFileType_DDS && sd.mip == -1)
sd.mip = 0;
// only DDS supports writing multiple slices, fall back to slice 0 if 'all slices' was specified
if(sd.destType != eFileType_DDS && sd.slice.sliceIndex == -1 && !sd.slice.slicesAsGrid &&
!sd.slice.cubeCruciform)
sd.slice.sliceIndex = 0;
// fetch source data subresources (typically only one, possibly more
// if we're writing to DDS (so writing multiple mips/slices) or resolving
// down a multisampled texture for writing as a single 'image' elsewhere)
uint32_t sliceOffset = 0;
uint32_t sliceStride = 1;
uint32_t numSlices = td.arraysize * td.depth;
uint32_t mipOffset = 0;
uint32_t numMips = td.mips;
bool singleSlice = (sd.slice.sliceIndex != -1);
// set which slices/mips we need
if(multisampled)
{
bool singleSample = !sd.sample.mapToArray;
// multisampled images have no mips
mipOffset = 0;
numMips = 1;
if(singleSlice)
{
if(singleSample)
{
// we want a specific sample in a specific real slice
sliceOffset = sd.slice.sliceIndex * sampleCount + sd.sample.sampleIndex;
numSlices = 1;
}
else
{
// we want all the samples (now mapped to slices) in a specific real slice
sliceOffset = sd.slice.sliceIndex;
numSlices = sampleCount;
}
}
else
{
if(singleSample)
{
// we want one sample in every slice, so we have to set the stride to sampleCount
// to skip every other sample (mapped to slices), starting from the sample we want
// in the first real slice
sliceOffset = sd.sample.sampleIndex;
sliceStride = sampleCount;
numSlices = RDCMAX(1U, td.arraysize / sampleCount);
}
else
{
// we want all slices, all samples
sliceOffset = 0;
numSlices = td.arraysize;
}
}
}
else
{
if(singleSlice)
{
numSlices = 1;
sliceOffset = sd.slice.sliceIndex;
}
// otherwise take all slices, as by default
if(sd.mip != -1)
{
mipOffset = sd.mip;
numMips = 1;
}
// otherwise take all mips, as by default
}
vector<byte *> subdata;
bool downcast = false;
// don't support slice mappings for DDS - it supports slices natively
if(sd.destType == eFileType_DDS)
{
sd.slice.cubeCruciform = false;
sd.slice.slicesAsGrid = false;
}
// force downcast to be able to do grid mappings
if(sd.slice.cubeCruciform || sd.slice.slicesAsGrid)
downcast = true;
// we don't support any file formats that handle these block compression formats
if(td.format.specialFormat == eSpecial_ETC2 || td.format.specialFormat == eSpecial_EAC ||
td.format.specialFormat == eSpecial_ASTC)
downcast = true;
// for DDS don't downcast, for non-HDR always downcast if we're not already RGBA8 unorm
// for HDR&EXR we can convert from most regular types as well as 10.10.10.2 and 11.11.10
if((sd.destType != eFileType_DDS && sd.destType != eFileType_HDR && sd.destType != eFileType_EXR &&
(td.format.compByteWidth != 1 || td.format.compType != eCompType_UNorm || td.format.bgraOrder)) ||
downcast || (sd.destType != eFileType_DDS && td.format.special &&
td.format.specialFormat != eSpecial_R10G10B10A2 &&
td.format.specialFormat != eSpecial_R11G11B10))
{
downcast = true;
td.format.compByteWidth = 1;
td.format.compCount = 4;
td.format.compType = eCompType_UNorm;
td.format.special = false;
td.format.specialFormat = eSpecial_Unknown;
}
uint32_t rowPitch = 0;
uint32_t slicePitch = 0;
bool blockformat = false;
int blockSize = 0;
uint32_t bytesPerPixel = 1;
td.width = RDCMAX(1U, td.width >> mipOffset);
td.height = RDCMAX(1U, td.height >> mipOffset);
td.depth = RDCMAX(1U, td.depth >> mipOffset);
if(td.format.specialFormat == eSpecial_BC1 || td.format.specialFormat == eSpecial_BC2 ||
td.format.specialFormat == eSpecial_BC3 || td.format.specialFormat == eSpecial_BC4 ||
td.format.specialFormat == eSpecial_BC5 || td.format.specialFormat == eSpecial_BC6 ||
td.format.specialFormat == eSpecial_BC7)
{
blockSize = (td.format.specialFormat == eSpecial_BC1 || td.format.specialFormat == eSpecial_BC4)
? 8
: 16;
rowPitch = RDCMAX(1U, ((td.width + 3) / 4)) * blockSize;
slicePitch = rowPitch * RDCMAX(1U, td.height / 4);
blockformat = true;
}
else
{
switch(td.format.specialFormat)
{
case eSpecial_S8: bytesPerPixel = 1; break;
case eSpecial_R10G10B10A2:
case eSpecial_R9G9B9E5:
case eSpecial_R11G11B10:
case eSpecial_D24S8: bytesPerPixel = 4; break;
case eSpecial_R5G6B5:
case eSpecial_R5G5B5A1:
case eSpecial_R4G4B4A4: bytesPerPixel = 2; break;
case eSpecial_D32S8: bytesPerPixel = 8; break;
case eSpecial_D16S8:
case eSpecial_YUV:
case eSpecial_R4G4:
RDCERR("Unsupported file format %u", td.format.specialFormat);
return false;
default: bytesPerPixel = td.format.compCount * td.format.compByteWidth;
}
rowPitch = td.width * bytesPerPixel;
slicePitch = rowPitch * td.height;
}
// loop over fetching subresources
for(uint32_t s = 0; s < numSlices; s++)
{
uint32_t slice = s * sliceStride + sliceOffset;
for(uint32_t m = 0; m < numMips; m++)
{
uint32_t mip = m + mipOffset;
size_t datasize = 0;
byte *bytes =
m_pDevice->GetTextureData(liveid, slice, mip, true, sd.typeHint, resolveSamples, downcast,
sd.comp.blackPoint, sd.comp.whitePoint, datasize);
if(bytes == NULL)
{
RDCERR("Couldn't get bytes for mip %u, slice %u", mip, slice);
for(size_t i = 0; i < subdata.size(); i++)
delete[] subdata[i];
return false;
}
if(td.depth == 1)
{
subdata.push_back(bytes);
continue;
}
uint32_t mipSlicePitch = slicePitch;
uint32_t w = RDCMAX(1U, td.width >> m);
uint32_t h = RDCMAX(1U, td.height >> m);
uint32_t d = RDCMAX(1U, td.depth >> m);
if(blockformat)
{
mipSlicePitch = RDCMAX(1U, ((w + 3) / 4)) * blockSize * RDCMAX(1U, h / 4);
}
else
{
mipSlicePitch = w * bytesPerPixel * h;
}
// we don't support slice ranges, only all-or-nothing
// we're also not dealing with multisampled slices if
// depth > 1. So if we only want one slice out of a 3D texture
// then make sure we get it
if(numSlices == 1)
{
byte *depthslice = new byte[mipSlicePitch];
byte *b = bytes + mipSlicePitch * sliceOffset;
memcpy(depthslice, b, slicePitch);
subdata.push_back(depthslice);
delete[] bytes;
continue;
}
s += (d - 1);
byte *b = bytes;
// add each depth slice as a separate subdata
for(uint32_t di = 0; di < d; di++)
{
byte *depthslice = new byte[mipSlicePitch];
memcpy(depthslice, b, mipSlicePitch);
subdata.push_back(depthslice);
b += mipSlicePitch;
}
delete[] bytes;
}
}
// should have been handled above, but verify incoming data is RGBA8
if(sd.slice.slicesAsGrid && td.format.compByteWidth == 1 && td.format.compCount == 4)
{
uint32_t sliceWidth = td.width;
uint32_t sliceHeight = td.height;
uint32_t sliceGridHeight = (td.arraysize * td.depth) / sd.slice.sliceGridWidth;
if((td.arraysize * td.depth) % sd.slice.sliceGridWidth != 0)
sliceGridHeight++;
td.width *= sd.slice.sliceGridWidth;
td.height *= sliceGridHeight;
byte *combinedData = new byte[td.width * td.height * td.format.compCount];
memset(combinedData, 0, td.width * td.height * td.format.compCount);
for(size_t i = 0; i < subdata.size(); i++)
{
uint32_t gridx = (uint32_t)i % sd.slice.sliceGridWidth;
uint32_t gridy = (uint32_t)i / sd.slice.sliceGridWidth;
uint32_t yoffs = gridy * sliceHeight;
uint32_t xoffs = gridx * sliceWidth;
for(uint32_t y = 0; y < sliceHeight; y++)
{
for(uint32_t x = 0; x < sliceWidth; x++)
{
uint32_t *srcpix = (uint32_t *)&subdata[i][(y * sliceWidth + x) * 4 + 0];
uint32_t *dstpix = (uint32_t *)&combinedData[((y + yoffs) * td.width + x + xoffs) * 4 + 0];
*dstpix = *srcpix;
}
}
delete[] subdata[i];
}
subdata.resize(1);
subdata[0] = combinedData;
rowPitch = td.width * 4;
}
// should have been handled above, but verify incoming data is RGBA8 and 6 slices
if(sd.slice.cubeCruciform && td.format.compByteWidth == 1 && td.format.compCount == 4 &&
subdata.size() == 6)
{
uint32_t sliceWidth = td.width;
uint32_t sliceHeight = td.height;
td.width *= 4;
td.height *= 3;
byte *combinedData = new byte[td.width * td.height * td.format.compCount];
memset(combinedData, 0, td.width * td.height * td.format.compCount);
/*
Y X=0 1 2 3
= +---+
0 |+y |
|[2]|
+---+---+---+---+
1 |-x |+z |+x |-z |
|[1]|[4]|[0]|[5]|
+---+---+---+---+
2 |-y |
|[3]|
+---+
*/
uint32_t gridx[6] = {2, 0, 1, 1, 1, 3};
uint32_t gridy[6] = {1, 1, 0, 2, 1, 1};
for(size_t i = 0; i < subdata.size(); i++)
{
uint32_t yoffs = gridy[i] * sliceHeight;
uint32_t xoffs = gridx[i] * sliceWidth;
for(uint32_t y = 0; y < sliceHeight; y++)
{
for(uint32_t x = 0; x < sliceWidth; x++)
{
uint32_t *srcpix = (uint32_t *)&subdata[i][(y * sliceWidth + x) * 4 + 0];
uint32_t *dstpix = (uint32_t *)&combinedData[((y + yoffs) * td.width + x + xoffs) * 4 + 0];
*dstpix = *srcpix;
}
}
delete[] subdata[i];
}
subdata.resize(1);
subdata[0] = combinedData;
rowPitch = td.width * 4;
}
int numComps = td.format.compCount;
// if we want a grayscale image of one channel, splat it across all channels
// and set alpha to full
if(sd.channelExtract >= 0 && td.format.compByteWidth == 1 &&
(uint32_t)sd.channelExtract < td.format.compCount)
{
uint32_t cc = td.format.compCount;
for(uint32_t y = 0; y < td.height; y++)
{
for(uint32_t x = 0; x < td.width; x++)
{
subdata[0][(y * td.width + x) * cc + 0] =
subdata[0][(y * td.width + x) * cc + sd.channelExtract];
if(cc >= 2)
subdata[0][(y * td.width + x) * cc + 1] =
subdata[0][(y * td.width + x) * cc + sd.channelExtract];
if(cc >= 3)
subdata[0][(y * td.width + x) * cc + 2] =
subdata[0][(y * td.width + x) * cc + sd.channelExtract];
if(cc >= 4)
subdata[0][(y * td.width + x) * cc + 3] = 255;
}
}
}
// handle formats that don't support alpha
if(numComps == 4 && (sd.destType == eFileType_BMP || sd.destType == eFileType_JPG))
{
byte *nonalpha = new byte[td.width * td.height * 3];
for(uint32_t y = 0; y < td.height; y++)
{
for(uint32_t x = 0; x < td.width; x++)
{
byte r = subdata[0][(y * td.width + x) * 4 + 0];
byte g = subdata[0][(y * td.width + x) * 4 + 1];
byte b = subdata[0][(y * td.width + x) * 4 + 2];
byte a = subdata[0][(y * td.width + x) * 4 + 3];
if(sd.alpha != eAlphaMap_Discard)
{
FloatVector col = sd.alphaCol;
if(sd.alpha == eAlphaMap_BlendToCheckerboard)
{
bool lightSquare = ((x / 64) % 2) == ((y / 64) % 2);
col = lightSquare ? sd.alphaCol : sd.alphaColSecondary;
}
col.x = powf(col.x, 1.0f / 2.2f);
col.y = powf(col.y, 1.0f / 2.2f);
col.z = powf(col.z, 1.0f / 2.2f);
FloatVector pixel = FloatVector(float(r) / 255.0f, float(g) / 255.0f, float(b) / 255.0f,
float(a) / 255.0f);
pixel.x = pixel.x * pixel.w + col.x * (1.0f - pixel.w);
pixel.y = pixel.y * pixel.w + col.y * (1.0f - pixel.w);
pixel.z = pixel.z * pixel.w + col.z * (1.0f - pixel.w);
r = byte(pixel.x * 255.0f);
g = byte(pixel.y * 255.0f);
b = byte(pixel.z * 255.0f);
}
nonalpha[(y * td.width + x) * 3 + 0] = r;
nonalpha[(y * td.width + x) * 3 + 1] = g;
nonalpha[(y * td.width + x) * 3 + 2] = b;
}
}
delete[] subdata[0];
subdata[0] = nonalpha;
numComps = 3;
rowPitch = td.width * 3;
}
// assume that (R,G,0) is better mapping than (Y,A) for 2 component data
if(numComps == 2 && (sd.destType == eFileType_BMP || sd.destType == eFileType_JPG ||
sd.destType == eFileType_PNG || sd.destType == eFileType_TGA))
{
byte *rg0 = new byte[td.width * td.height * 3];
for(uint32_t y = 0; y < td.height; y++)
{
for(uint32_t x = 0; x < td.width; x++)
{
byte r = subdata[0][(y * td.width + x) * 2 + 0];
byte g = subdata[0][(y * td.width + x) * 2 + 1];
rg0[(y * td.width + x) * 3 + 0] = r;
rg0[(y * td.width + x) * 3 + 1] = g;
rg0[(y * td.width + x) * 3 + 2] = 0;
// if we're greyscaling the image, then keep the greyscale here.
if(sd.channelExtract >= 0)
rg0[(y * td.width + x) * 3 + 2] = r;
}
}
delete[] subdata[0];
subdata[0] = rg0;
numComps = 3;
rowPitch = td.width * 3;
}
FILE *f = FileIO::fopen(path, "wb");
if(!f)
{
success = false;
}
else
{
if(sd.destType == eFileType_DDS)
{
dds_data ddsData;
ddsData.width = td.width;
ddsData.height = td.height;
ddsData.depth = td.depth;
ddsData.format = td.format;
ddsData.mips = numMips;
ddsData.slices = numSlices / td.depth;
ddsData.subdata = &subdata[0];
ddsData.cubemap = td.cubemap && numSlices == 6;
success = write_dds_to_file(f, ddsData);
}
else if(sd.destType == eFileType_BMP)
{
int ret = stbi_write_bmp_to_func(fileWriteFunc, (void *)f, td.width, td.height, numComps,
subdata[0]);
success = (ret != 0);
}
else if(sd.destType == eFileType_PNG)
{
int ret = stbi_write_png_to_func(fileWriteFunc, (void *)f, td.width, td.height, numComps,
subdata[0], rowPitch);
success = (ret != 0);
}
else if(sd.destType == eFileType_TGA)
{
int ret = stbi_write_tga_to_func(fileWriteFunc, (void *)f, td.width, td.height, numComps,
subdata[0]);
success = (ret != 0);
}
else if(sd.destType == eFileType_JPG)
{
jpge::params p;
p.m_quality = sd.jpegQuality;
int len = td.width * td.height * td.format.compCount;
// ensure buffer is at least 1024
if(len < 1024)
len = 1024;
char *jpgdst = new char[len];
success = jpge::compress_image_to_jpeg_file_in_memory(jpgdst, len, td.width, td.height,
numComps, subdata[0], p);
if(success)
fwrite(jpgdst, 1, len, f);
delete[] jpgdst;
}
else if(sd.destType == eFileType_HDR || sd.destType == eFileType_EXR)
{
float *fldata = NULL;
float *abgr[4] = {NULL, NULL, NULL, NULL};
if(sd.destType == eFileType_HDR)
{
fldata = new float[td.width * td.height * 4];
}
else
{
abgr[0] = new float[td.width * td.height];
abgr[1] = new float[td.width * td.height];
abgr[2] = new float[td.width * td.height];
abgr[3] = new float[td.width * td.height];
}
byte *srcData = subdata[0];
for(uint32_t y = 0; y < td.height; y++)
{
for(uint32_t x = 0; x < td.width; x++)
{
float r = 0.0f;
float g = 0.0f;
float b = 0.0f;
float a = 1.0f;
if(td.format.special && td.format.specialFormat == eSpecial_R10G10B10A2)
{
uint32_t *u32 = (uint32_t *)srcData;
Vec4f vec = ConvertFromR10G10B10A2(*u32);
r = vec.x;
g = vec.y;
b = vec.z;
a = vec.w;
srcData += 4;
}
else if(td.format.special && td.format.specialFormat == eSpecial_R11G11B10)
{
uint32_t *u32 = (uint32_t *)srcData;
Vec3f vec = ConvertFromR11G11B10(*u32);
r = vec.x;
g = vec.y;
b = vec.z;
a = 1.0f;
srcData += 4;
}
else
{
if(td.format.compCount >= 1)
r = ConvertComponent(td.format, srcData + td.format.compByteWidth * 0);
if(td.format.compCount >= 2)
g = ConvertComponent(td.format, srcData + td.format.compByteWidth * 1);
if(td.format.compCount >= 3)
b = ConvertComponent(td.format, srcData + td.format.compByteWidth * 2);
if(td.format.compCount >= 4)
a = ConvertComponent(td.format, srcData + td.format.compByteWidth * 3);
srcData += td.format.compCount * td.format.compByteWidth;
}
// HDR can't represent negative values
if(sd.destType == eFileType_HDR)
{
r = RDCMAX(r, 0.0f);
g = RDCMAX(g, 0.0f);
b = RDCMAX(b, 0.0f);
a = RDCMAX(a, 0.0f);
}
if(sd.channelExtract == 0)
{
g = b = r;
a = 1.0f;
}
if(sd.channelExtract == 1)
{
r = b = g;
a = 1.0f;
}
if(sd.channelExtract == 2)
{
r = g = b;
a = 1.0f;
}
if(sd.channelExtract == 3)
{
r = g = b = a;
a = 1.0f;
}
if(fldata)
{
fldata[(y * td.width + x) * 4 + 0] = r;
fldata[(y * td.width + x) * 4 + 1] = g;
fldata[(y * td.width + x) * 4 + 2] = b;
fldata[(y * td.width + x) * 4 + 3] = a;
}
else
{
abgr[0][(y * td.width + x)] = a;
abgr[1][(y * td.width + x)] = b;
abgr[2][(y * td.width + x)] = g;
abgr[3][(y * td.width + x)] = r;
}
}
}
if(sd.destType == eFileType_HDR)
{
int ret = stbi_write_hdr_to_func(fileWriteFunc, (void *)f, td.width, td.height, 4, fldata);
success = (ret != 0);
}
else if(sd.destType == eFileType_EXR)
{
const char *err = NULL;
EXRImage exrImage;
InitEXRImage(&exrImage);
int pixTypes[4] = {TINYEXR_PIXELTYPE_FLOAT, TINYEXR_PIXELTYPE_FLOAT,
TINYEXR_PIXELTYPE_FLOAT, TINYEXR_PIXELTYPE_FLOAT};
int reqTypes[4] = {TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF, TINYEXR_PIXELTYPE_HALF,
TINYEXR_PIXELTYPE_HALF};
// must be in this order as many viewers don't pay attention to channels and just assume
// they are in this order
const char *bgraNames[4] = {"A", "B", "G", "R"};
exrImage.num_channels = 4;
exrImage.channel_names = bgraNames;
exrImage.images = (unsigned char **)abgr;
exrImage.width = td.width;
exrImage.height = td.height;
exrImage.pixel_types = pixTypes;
exrImage.requested_pixel_types = reqTypes;
unsigned char *mem = NULL;
size_t ret = SaveMultiChannelEXRToMemory(&exrImage, &mem, &err);
success = (ret > 0);
if(success)
FileIO::fwrite(mem, 1, ret, f);
else
RDCERR("Error saving EXR file %d: '%s'", ret, err);
free(mem);
}
if(fldata)
{
delete[] fldata;
}
else
{
delete[] abgr[0];
delete[] abgr[1];
delete[] abgr[2];
delete[] abgr[3];
}
}
FileIO::fclose(f);
}
for(size_t i = 0; i < subdata.size(); i++)
delete[] subdata[i];
return success;
}
bool ReplayRenderer::PixelHistory(ResourceId target, uint32_t x, uint32_t y, uint32_t slice,
uint32_t mip, uint32_t sampleIdx, FormatComponentType typeHint,
rdctype::array<PixelModification> *history)
{
for(size_t t = 0; t < m_Textures.size(); t++)
{
if(m_Textures[t].ID == target)
{
if(x >= m_Textures[t].width || y >= m_Textures[t].height)
{
RDCDEBUG("PixelHistory out of bounds on %llu (%u,%u) vs (%u,%u)", target, x, y,
m_Textures[t].width, m_Textures[t].height);
history->count = 0;
history->elems = NULL;
return false;
}
if(m_Textures[t].msSamp == 1)
sampleIdx = ~0U;
slice = RDCCLAMP(slice, 0U, m_Textures[t].arraysize);
mip = RDCCLAMP(mip, 0U, m_Textures[t].mips);
break;
}
}
auto usage = m_pDevice->GetUsage(m_pDevice->GetLiveID(target));
vector<EventUsage> events;
for(size_t i = 0; i < usage.size(); i++)
{
if(usage[i].eventID > m_EventID)
continue;
switch(usage[i].usage)
{
case eUsage_VertexBuffer:
case eUsage_IndexBuffer:
case eUsage_VS_Constants:
case eUsage_HS_Constants:
case eUsage_DS_Constants:
case eUsage_GS_Constants:
case eUsage_PS_Constants:
case eUsage_CS_Constants:
case eUsage_VS_Resource:
case eUsage_HS_Resource:
case eUsage_DS_Resource:
case eUsage_GS_Resource:
case eUsage_PS_Resource:
case eUsage_CS_Resource:
case eUsage_InputTarget:
case eUsage_CopySrc:
case eUsage_ResolveSrc:
case eUsage_Barrier:
// read-only, not a valid pixel history event
continue;
case eUsage_None:
case eUsage_SO:
case eUsage_VS_RWResource:
case eUsage_HS_RWResource:
case eUsage_DS_RWResource:
case eUsage_GS_RWResource:
case eUsage_PS_RWResource:
case eUsage_CS_RWResource:
case eUsage_ColourTarget:
case eUsage_DepthStencilTarget:
case eUsage_Clear:
case eUsage_Copy:
case eUsage_CopyDst:
case eUsage_Resolve:
case eUsage_ResolveDst:
case eUsage_GenMips:
// writing - include in pixel history events
break;
}
events.push_back(usage[i]);
}
if(events.empty())
{
RDCDEBUG("Target %llu not written to before %u", target, m_EventID);
history->count = 0;
history->elems = NULL;
return false;
}
*history = m_pDevice->PixelHistory(events, m_pDevice->GetLiveID(target), x, y, slice, mip,
sampleIdx, typeHint);
SetFrameEvent(m_EventID, true);
return true;
}
bool ReplayRenderer::DebugVertex(uint32_t vertid, uint32_t instid, uint32_t idx,
uint32_t instOffset, uint32_t vertOffset, ShaderDebugTrace *trace)
{
if(trace == NULL)
return false;
*trace = m_pDevice->DebugVertex(m_EventID, vertid, instid, idx, instOffset, vertOffset);
SetFrameEvent(m_EventID, true);
return true;
}
bool ReplayRenderer::DebugPixel(uint32_t x, uint32_t y, uint32_t sample, uint32_t primitive,
ShaderDebugTrace *trace)
{
if(trace == NULL)
return false;
*trace = m_pDevice->DebugPixel(m_EventID, x, y, sample, primitive);
SetFrameEvent(m_EventID, true);
return true;
}
bool ReplayRenderer::DebugThread(uint32_t groupid[3], uint32_t threadid[3], ShaderDebugTrace *trace)
{
if(trace == NULL)
return false;
*trace = m_pDevice->DebugThread(m_EventID, groupid, threadid);
SetFrameEvent(m_EventID, true);
return true;
}
bool ReplayRenderer::GetCBufferVariableContents(ResourceId shader, const char *entryPoint,
uint32_t cbufslot, ResourceId buffer, uint64_t offs,
rdctype::array<ShaderVariable> *vars)
{
if(vars == NULL)
return false;
vector<byte> data;
if(buffer != ResourceId())
m_pDevice->GetBufferData(m_pDevice->GetLiveID(buffer), offs, 0, data);
vector<ShaderVariable> v;
m_pDevice->FillCBufferVariables(m_pDevice->GetLiveID(shader), entryPoint, cbufslot, v, data);
*vars = v;
return true;
}
void ReplayRenderer::GetSupportedWindowSystems(rdctype::array<WindowingSystem> *systems)
{
if(systems)
*systems = m_pDevice->GetSupportedWindowSystems();
}
ReplayOutput *ReplayRenderer::CreateOutput(WindowingSystem system, void *data, OutputType type)
{
ReplayOutput *out = new ReplayOutput(this, system, data, type);
m_Outputs.push_back(out);
m_pDevice->ReplayLog(m_EventID, eReplay_WithoutDraw);
out->SetFrameEvent(m_EventID);
m_pDevice->ReplayLog(m_EventID, eReplay_OnlyDraw);
return out;
}
void ReplayRenderer::ShutdownOutput(ReplayOutput *output)
{
RDCUNIMPLEMENTED("Shutting down individual outputs");
}
void ReplayRenderer::Shutdown()
{
delete this;
}
ResourceId ReplayRenderer::BuildTargetShader(const char *entry, const char *source,
const uint32_t compileFlags, ShaderStageType type,
rdctype::str *errors)
{
ResourceId id;
string errs;
switch(type)
{
case eShaderStage_Vertex:
case eShaderStage_Hull:
case eShaderStage_Domain:
case eShaderStage_Geometry:
case eShaderStage_Pixel:
case eShaderStage_Compute: break;
default: RDCERR("Unexpected type in BuildShader!"); return ResourceId();
}
m_pDevice->BuildTargetShader(source, entry, compileFlags, type, &id, &errs);
if(id != ResourceId())
m_TargetResources.insert(id);
if(errors)
*errors = errs;
return id;
}
ResourceId ReplayRenderer::BuildCustomShader(const char *entry, const char *source,
const uint32_t compileFlags, ShaderStageType type,
rdctype::str *errors)
{
ResourceId id;
string errs;
switch(type)
{
case eShaderStage_Vertex:
case eShaderStage_Hull:
case eShaderStage_Domain:
case eShaderStage_Geometry:
case eShaderStage_Pixel:
case eShaderStage_Compute: break;
default: RDCERR("Unexpected type in BuildShader!"); return ResourceId();
}
m_pDevice->BuildCustomShader(source, entry, compileFlags, type, &id, &errs);
if(id != ResourceId())
m_CustomShaders.insert(id);
if(errors)
*errors = errs;
return id;
}
bool ReplayRenderer::FreeTargetResource(ResourceId id)
{
m_TargetResources.erase(id);
m_pDevice->FreeTargetResource(id);
return true;
}
bool ReplayRenderer::FreeCustomShader(ResourceId id)
{
m_CustomShaders.erase(id);
m_pDevice->FreeCustomShader(id);
return true;
}
bool ReplayRenderer::ReplaceResource(ResourceId from, ResourceId to)
{
m_pDevice->ReplaceResource(from, to);
SetFrameEvent(m_EventID, true);
for(size_t i = 0; i < m_Outputs.size(); i++)
if(m_Outputs[i]->GetType() != eOutputType_None)
m_Outputs[i]->Display();
return true;
}
bool ReplayRenderer::RemoveReplacement(ResourceId id)
{
m_pDevice->RemoveReplacement(id);
SetFrameEvent(m_EventID, true);
for(size_t i = 0; i < m_Outputs.size(); i++)
if(m_Outputs[i]->GetType() != eOutputType_None)
m_Outputs[i]->Display();
return true;
}
ReplayCreateStatus ReplayRenderer::CreateDevice(const char *logfile)
{
RDCLOG("Creating replay device for %s", logfile);
RDCDriver driverType = RDC_Unknown;
string driverName = "";
uint64_t fileMachineIdent = 0;
auto status =
RenderDoc::Inst().FillInitParams(logfile, driverType, driverName, fileMachineIdent, NULL);
if(driverType == RDC_Unknown || driverName == "" || status != eReplayCreate_Success)
{
RDCERR("Couldn't get device type from log");
return status;
}
IReplayDriver *driver = NULL;
status = RenderDoc::Inst().CreateReplayDriver(driverType, logfile, &driver);
if(driver && status == eReplayCreate_Success)
{
RDCLOG("Created replay driver.");
return PostCreateInit(driver);
}
RDCERR("Couldn't create a replay device :(.");
return status;
}
ReplayCreateStatus ReplayRenderer::SetDevice(IReplayDriver *device)
{
if(device)
{
RDCLOG("Got replay driver.");
return PostCreateInit(device);
}
RDCERR("Given invalid replay driver.");
return eReplayCreate_InternalError;
}
ReplayCreateStatus ReplayRenderer::PostCreateInit(IReplayDriver *device)
{
m_pDevice = device;
m_pDevice->ReadLogInitialisation();
FetchPipelineState();
FetchFrameRecord fr = m_pDevice->GetFrameRecord();
m_FrameRecord.frameInfo = fr.frameInfo;
m_FrameRecord.m_DrawCallList = fr.drawcallList;
SetupDrawcallPointers(&m_Drawcalls, m_FrameRecord.m_DrawCallList, NULL, NULL);
return eReplayCreate_Success;
}
void ReplayRenderer::FileChanged()
{
m_pDevice->FileChanged();
}
bool ReplayRenderer::HasCallstacks()
{
return m_pDevice->HasCallstacks();
}
APIProperties ReplayRenderer::GetAPIProperties()
{
return m_pDevice->GetAPIProperties();
}
bool ReplayRenderer::InitResolver()
{
m_pDevice->InitCallstackResolver();
return m_pDevice->GetCallstackResolver() != NULL;
}
void ReplayRenderer::FetchPipelineState()
{
m_pDevice->SavePipelineState();
m_D3D11PipelineState = m_pDevice->GetD3D11PipelineState();
m_GLPipelineState = m_pDevice->GetGLPipelineState();
m_VulkanPipelineState = m_pDevice->GetVulkanPipelineState();
{
D3D11PipelineState::ShaderStage *stages[] = {
&m_D3D11PipelineState.m_VS, &m_D3D11PipelineState.m_HS, &m_D3D11PipelineState.m_DS,
&m_D3D11PipelineState.m_GS, &m_D3D11PipelineState.m_PS, &m_D3D11PipelineState.m_CS,
};
for(int i = 0; i < 6; i++)
if(stages[i]->Shader != ResourceId())
stages[i]->ShaderDetails = m_pDevice->GetShader(m_pDevice->GetLiveID(stages[i]->Shader), "");
}
{
GLPipelineState::ShaderStage *stages[] = {
&m_GLPipelineState.m_VS, &m_GLPipelineState.m_TCS, &m_GLPipelineState.m_TES,
&m_GLPipelineState.m_GS, &m_GLPipelineState.m_FS, &m_GLPipelineState.m_CS,
};
for(int i = 0; i < 6; i++)
if(stages[i]->Shader != ResourceId())
stages[i]->ShaderDetails = m_pDevice->GetShader(m_pDevice->GetLiveID(stages[i]->Shader), "");
}
{
VulkanPipelineState::ShaderStage *stages[] = {
&m_VulkanPipelineState.VS, &m_VulkanPipelineState.TCS, &m_VulkanPipelineState.TES,
&m_VulkanPipelineState.GS, &m_VulkanPipelineState.FS, &m_VulkanPipelineState.CS,
};
for(int i = 0; i < 6; i++)
if(stages[i]->Shader != ResourceId())
stages[i]->ShaderDetails = m_pDevice->GetShader(m_pDevice->GetLiveID(stages[i]->Shader),
stages[i]->entryPoint.elems);
}
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_GetAPIProperties(ReplayRenderer *rend,
APIProperties *props)
{
if(props)
*props = rend->GetAPIProperties();
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_GetSupportedWindowSystems(
ReplayRenderer *rend, rdctype::array<WindowingSystem> *systems)
{
rend->GetSupportedWindowSystems(systems);
}
extern "C" RENDERDOC_API ReplayOutput *RENDERDOC_CC ReplayRenderer_CreateOutput(
ReplayRenderer *rend, WindowingSystem system, void *data, OutputType type)
{
return rend->CreateOutput(system, data, type);
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_Shutdown(ReplayRenderer *rend)
{
rend->Shutdown();
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_ShutdownOutput(ReplayRenderer *rend,
ReplayOutput *output)
{
rend->ShutdownOutput(output);
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_FileChanged(ReplayRenderer *rend)
{
rend->FileChanged();
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_HasCallstacks(ReplayRenderer *rend)
{
return rend->HasCallstacks();
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_InitResolver(ReplayRenderer *rend)
{
return rend->InitResolver();
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_SetFrameEvent(ReplayRenderer *rend,
uint32_t eventID,
bool32 force)
{
return rend->SetFrameEvent(eventID, force != 0);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetD3D11PipelineState(ReplayRenderer *rend, D3D11PipelineState *state)
{
return rend->GetD3D11PipelineState(state);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetGLPipelineState(ReplayRenderer *rend,
GLPipelineState *state)
{
return rend->GetGLPipelineState(state);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetVulkanPipelineState(ReplayRenderer *rend, VulkanPipelineState *state)
{
return rend->GetVulkanPipelineState(state);
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_BuildCustomShader(
ReplayRenderer *rend, const char *entry, const char *source, const uint32_t compileFlags,
ShaderStageType type, ResourceId *shaderID, rdctype::str *errors)
{
if(shaderID == NULL)
return;
*shaderID = rend->BuildCustomShader(entry, source, compileFlags, type, errors);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_FreeCustomShader(ReplayRenderer *rend,
ResourceId id)
{
return rend->FreeCustomShader(id);
}
extern "C" RENDERDOC_API void RENDERDOC_CC ReplayRenderer_BuildTargetShader(
ReplayRenderer *rend, const char *entry, const char *source, const uint32_t compileFlags,
ShaderStageType type, ResourceId *shaderID, rdctype::str *errors)
{
if(shaderID == NULL)
return;
*shaderID = rend->BuildTargetShader(entry, source, compileFlags, type, errors);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_ReplaceResource(ReplayRenderer *rend,
ResourceId from,
ResourceId to)
{
return rend->ReplaceResource(from, to);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_RemoveReplacement(ReplayRenderer *rend,
ResourceId id)
{
return rend->RemoveReplacement(id);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_FreeTargetResource(ReplayRenderer *rend,
ResourceId id)
{
return rend->FreeTargetResource(id);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetFrameInfo(ReplayRenderer *rend,
FetchFrameInfo *frame)
{
return rend->GetFrameInfo(frame);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetDrawcalls(ReplayRenderer *rend, rdctype::array<FetchDrawcall> *draws)
{
return rend->GetDrawcalls(draws);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_FetchCounters(ReplayRenderer *rend, uint32_t *counters, uint32_t numCounters,
rdctype::array<CounterResult> *results)
{
return rend->FetchCounters(counters, numCounters, results);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_EnumerateCounters(ReplayRenderer *rend, rdctype::array<uint32_t> *counters)
{
return rend->EnumerateCounters(counters);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_DescribeCounter(ReplayRenderer *rend,
uint32_t counterID,
CounterDescription *desc)
{
return rend->DescribeCounter(counterID, desc);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetTextures(ReplayRenderer *rend, rdctype::array<FetchTexture> *texs)
{
return rend->GetTextures(texs);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetBuffers(ReplayRenderer *rend, rdctype::array<FetchBuffer> *bufs)
{
return rend->GetBuffers(bufs);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetResolve(ReplayRenderer *rend, uint64_t *callstack, uint32_t callstackLen,
rdctype::array<rdctype::str> *trace)
{
return rend->GetResolve(callstack, callstackLen, trace);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetDebugMessages(ReplayRenderer *rend, rdctype::array<DebugMessage> *msgs)
{
return rend->GetDebugMessages(msgs);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_PixelHistory(
ReplayRenderer *rend, ResourceId target, uint32_t x, uint32_t y, uint32_t slice, uint32_t mip,
uint32_t sampleIdx, FormatComponentType typeHint, rdctype::array<PixelModification> *history)
{
return rend->PixelHistory(target, x, y, slice, mip, sampleIdx, typeHint, history);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_DebugVertex(ReplayRenderer *rend, uint32_t vertid, uint32_t instid, uint32_t idx,
uint32_t instOffset, uint32_t vertOffset, ShaderDebugTrace *trace)
{
return rend->DebugVertex(vertid, instid, idx, instOffset, vertOffset, trace);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_DebugPixel(ReplayRenderer *rend,
uint32_t x, uint32_t y,
uint32_t sample,
uint32_t primitive,
ShaderDebugTrace *trace)
{
return rend->DebugPixel(x, y, sample, primitive, trace);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_DebugThread(ReplayRenderer *rend,
uint32_t groupid[3],
uint32_t threadid[3],
ShaderDebugTrace *trace)
{
return rend->DebugThread(groupid, threadid, trace);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetUsage(ReplayRenderer *rend, ResourceId id, rdctype::array<EventUsage> *usage)
{
return rend->GetUsage(id, usage);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetCBufferVariableContents(
ReplayRenderer *rend, ResourceId shader, const char *entryPoint, uint32_t cbufslot,
ResourceId buffer, uint64_t offs, rdctype::array<ShaderVariable> *vars)
{
return rend->GetCBufferVariableContents(shader, entryPoint, cbufslot, buffer, offs, vars);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_SaveTexture(ReplayRenderer *rend,
const TextureSave &saveData,
const char *path)
{
return rend->SaveTexture(saveData, path);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetPostVSData(ReplayRenderer *rend,
uint32_t instID,
MeshDataStage stage,
MeshFormat *data)
{
return rend->GetPostVSData(instID, stage, data);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetMinMax(
ReplayRenderer *rend, ResourceId tex, uint32_t sliceFace, uint32_t mip, uint32_t sample,
FormatComponentType typeHint, PixelValue *minval, PixelValue *maxval)
{
return rend->GetMinMax(tex, sliceFace, mip, sample, typeHint, minval, maxval);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC
ReplayRenderer_GetHistogram(ReplayRenderer *rend, ResourceId tex, uint32_t sliceFace, uint32_t mip,
uint32_t sample, FormatComponentType typeHint, float minval,
float maxval, bool32 channels[4], rdctype::array<uint32_t> *histogram)
{
bool chans[4] = {channels[0] != 0, channels[1] != 0, channels[2] != 0, channels[3] != 0};
return rend->GetHistogram(tex, sliceFace, mip, sample, typeHint, minval, maxval, chans, histogram);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetBufferData(
ReplayRenderer *rend, ResourceId buff, uint64_t offset, uint64_t len, rdctype::array<byte> *data)
{
return rend->GetBufferData(buff, offset, len, data);
}
extern "C" RENDERDOC_API bool32 RENDERDOC_CC ReplayRenderer_GetTextureData(
ReplayRenderer *rend, ResourceId tex, uint32_t arrayIdx, uint32_t mip, rdctype::array<byte> *data)
{
return rend->GetTextureData(tex, arrayIdx, mip, data);
}