mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-05-12 13:00:32 +00:00
Move SaveTexture logic to replay layer
* Expand the abilities of the GetTextureData in replay drivers to be able to resolve samples and render down to RGBA8 unorm for file export to other programs. * Greatly improve the ability to save textures - in theory any texture format/type/dimension/etc should now be mappable in sensible & useful ways to output formats.
This commit is contained in:
@@ -76,6 +76,88 @@ struct TextureDisplay
|
||||
TextureDisplayOverlay overlay;
|
||||
};
|
||||
|
||||
struct TextureSave
|
||||
{
|
||||
ResourceId id;
|
||||
|
||||
FileType destType;
|
||||
|
||||
// mip == -1 writes out all mips where allowed by file format
|
||||
// or writes mip 0 otherwise
|
||||
int32_t mip;
|
||||
|
||||
// for output formats that are 8bit unorm srgb, values are mapped using
|
||||
// the following black/white points.
|
||||
struct ComponentMapping
|
||||
{
|
||||
float blackPoint;
|
||||
float whitePoint;
|
||||
} comp;
|
||||
|
||||
// what to do for multisampled textures (ignored otherwise)
|
||||
struct SampleMapping
|
||||
{
|
||||
// if true, texture acts like an array, each slice being
|
||||
// the corresponding sample, and below sample index is ignored.
|
||||
// Later options for handling slices/faces then control how
|
||||
// a texture array is mapped to the file.
|
||||
bool32 mapToArray;
|
||||
|
||||
// if the above mapToArray is false, this selects the sample
|
||||
// index to treat as a normal 2D image. If this is ~0U a default
|
||||
// unweighted average resolve is performed instead.
|
||||
// resolve only available for uncompressed simple formats.
|
||||
uint32_t sampleIndex;
|
||||
} sample;
|
||||
|
||||
// how to select/save depth/array slices or cubemap faces
|
||||
// if invalid options are specified, slice index 0 is written
|
||||
// alone
|
||||
struct SliceMapping
|
||||
{
|
||||
// select the (depth/array) slice to save.
|
||||
// If this is -1, writes out all slices as detailed below
|
||||
// this is only supported in formats that don't support
|
||||
// slices natively, and will be done in RGBA8 space.
|
||||
int32_t sliceIndex;
|
||||
|
||||
// write out the slices as a 2D grid, with the below
|
||||
// width. Any empty slices are writted as (0,0,0,0)
|
||||
bool32 slicesAsGrid;
|
||||
|
||||
int32_t sliceGridWidth;
|
||||
|
||||
// write out 6 slices in the cruciform:
|
||||
/*
|
||||
+---+
|
||||
|+y |
|
||||
| |
|
||||
+---+---+---+---+
|
||||
|-x |+z |+x |-z |
|
||||
| | | | |
|
||||
+---+---+---+---+
|
||||
|-y |
|
||||
| |
|
||||
+---+
|
||||
*/
|
||||
// with the gaps filled with (0,0,0,0)
|
||||
bool32 cubeCruciform;
|
||||
|
||||
// if sliceIndex is -1, cubeCruciform == slicesAsGrid == false
|
||||
// and file format doesn't support saving all slices, only
|
||||
// slice 0 is saved
|
||||
} slice;
|
||||
|
||||
// for formats without an alpha channel, define how it should be
|
||||
// mapped. Only available for uncompressed simple formats, done
|
||||
// in RGBA8 space.
|
||||
AlphaMapping alpha;
|
||||
FloatVector alphaCol;
|
||||
FloatVector alphaColSecondary;
|
||||
|
||||
int jpegQuality;
|
||||
};
|
||||
|
||||
struct RemoteMessage
|
||||
{
|
||||
RemoteMessage() {}
|
||||
|
||||
@@ -161,7 +161,7 @@ extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_GetUsage(ReplayRendere
|
||||
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_GetCBufferVariableContents(ReplayRenderer *rend, ResourceId shader, uint32_t cbufslot, ResourceId buffer, uint32_t offs, rdctype::array<ShaderVariable> *vars);
|
||||
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_SaveTexture(ReplayRenderer *rend, ResourceId texID, uint32_t mip, const wchar_t *path);
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_SaveTexture(ReplayRenderer *rend, const TextureSave &saveData, const wchar_t *path);
|
||||
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_GetPostVSData(ReplayRenderer *rend, MeshDataStage stage, PostVSMeshData *data);
|
||||
|
||||
|
||||
@@ -126,6 +126,23 @@ enum TextureDisplayOverlay
|
||||
eTexOverlay_QuadOverdrawDraw,
|
||||
};
|
||||
|
||||
enum FileType
|
||||
{
|
||||
eFileType_DDS,
|
||||
eFileType_PNG,
|
||||
eFileType_JPG,
|
||||
eFileType_BMP,
|
||||
eFileType_TGA,
|
||||
eFileType_HDR,
|
||||
};
|
||||
|
||||
enum AlphaMapping
|
||||
{
|
||||
eAlphaMap_Discard,
|
||||
eAlphaMap_BlendToColour,
|
||||
eAlphaMap_BlendToCheckerboard,
|
||||
};
|
||||
|
||||
enum SpecialFormat
|
||||
{
|
||||
eSpecial_Unknown = 0,
|
||||
|
||||
@@ -703,7 +703,7 @@ void ProxySerialiser::EnsureCached(ResourceId texid, uint32_t arrayIdx, uint32_t
|
||||
ResourceId proxyid = m_ProxyTextureIds[texid];
|
||||
|
||||
size_t size;
|
||||
byte *data = GetTextureData(texid, arrayIdx, mip, size);
|
||||
byte *data = GetTextureData(texid, arrayIdx, mip, false, false, 0.0f, 0.0f, size);
|
||||
|
||||
if(data)
|
||||
m_Proxy->SetProxyTextureData(proxyid, arrayIdx, mip, data, size);
|
||||
@@ -799,7 +799,7 @@ bool ProxySerialiser::Tick()
|
||||
case eCommand_GetTextureData:
|
||||
{
|
||||
size_t dummy;
|
||||
GetTextureData(ResourceId(), 0, 0, dummy);
|
||||
GetTextureData(ResourceId(), 0, 0, false, false, 0.0f, 0.0f, dummy);
|
||||
break;
|
||||
}
|
||||
case eCommand_InitPostVS:
|
||||
@@ -1222,15 +1222,20 @@ vector<byte> ProxySerialiser::GetBufferData(ResourceId buff, uint32_t offset, ui
|
||||
return ret;
|
||||
}
|
||||
|
||||
byte *ProxySerialiser::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize)
|
||||
byte *ProxySerialiser::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm,
|
||||
float blackPoint, float whitePoint, size_t &dataSize)
|
||||
{
|
||||
m_ToReplaySerialiser->Serialise("", tex);
|
||||
m_ToReplaySerialiser->Serialise("", arrayIdx);
|
||||
m_ToReplaySerialiser->Serialise("", mip);
|
||||
m_ToReplaySerialiser->Serialise("", resolve);
|
||||
m_ToReplaySerialiser->Serialise("", forceRGBA8unorm);
|
||||
m_ToReplaySerialiser->Serialise("", blackPoint);
|
||||
m_ToReplaySerialiser->Serialise("", whitePoint);
|
||||
|
||||
if(m_ReplayHost)
|
||||
{
|
||||
byte *data = m_Remote->GetTextureData(tex, arrayIdx, mip, dataSize);
|
||||
byte *data = m_Remote->GetTextureData(tex, arrayIdx, mip, resolve, forceRGBA8unorm, blackPoint, whitePoint, dataSize);
|
||||
|
||||
byte *compressed = new byte[dataSize+512];
|
||||
|
||||
|
||||
@@ -191,17 +191,6 @@ class ProxySerialiser : public IReplayDriver, Callstack::StackResolver
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SaveTexture(ResourceId tex, uint32_t saveMip, wstring path)
|
||||
{
|
||||
if(m_Proxy)
|
||||
{
|
||||
EnsureCached(tex, 0, saveMip);
|
||||
return m_Proxy->SaveTexture(m_ProxyTextureIds[tex], saveMip, path);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RenderTexture(TextureDisplay cfg)
|
||||
{
|
||||
@@ -293,7 +282,7 @@ class ProxySerialiser : public IReplayDriver, Callstack::StackResolver
|
||||
void FillCBufferVariables(ResourceId shader, uint32_t cbufSlot, vector<ShaderVariable> &outvars, const vector<byte> &data);
|
||||
|
||||
vector<byte> GetBufferData(ResourceId buff, uint32_t offset, uint32_t len);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize);
|
||||
|
||||
void InitPostVSBuffers(uint32_t frameID, uint32_t eventID);
|
||||
PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage);
|
||||
|
||||
@@ -1715,7 +1715,8 @@ void D3D11DebugManager::PickPixel(ResourceId texture, uint32_t x, uint32_t y, ui
|
||||
m_pImmediateContext->Unmap(m_DebugRender.PickPixelStageTex, 0);
|
||||
}
|
||||
|
||||
byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32_t mip, size_t &dataSize)
|
||||
byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm,
|
||||
float blackPoint, float whitePoint, size_t &dataSize)
|
||||
{
|
||||
ID3D11Resource *dummyTex = NULL;
|
||||
|
||||
@@ -1742,6 +1743,12 @@ byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32
|
||||
mips = desc.MipLevels ? desc.MipLevels : CalcNumMips(desc.Width, 1, 1);
|
||||
|
||||
if(mip >= mips || arrayIdx >= desc.ArraySize) return NULL;
|
||||
|
||||
if(forceRGBA8unorm)
|
||||
{
|
||||
desc.Format = IsSRGBFormat(desc.Format) ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.ArraySize = 1;
|
||||
}
|
||||
|
||||
subresource = arrayIdx*mips + mip;
|
||||
|
||||
@@ -1757,7 +1764,85 @@ byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32
|
||||
|
||||
bytesize = GetByteSize(desc.Width, 1, 1, desc.Format, mip);
|
||||
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture1D, d), wrapTex->GetReal());
|
||||
if(forceRGBA8unorm)
|
||||
{
|
||||
subresource = mip;
|
||||
|
||||
desc.CPUAccessFlags = 0;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
ID3D11Texture1D *rtTex = NULL;
|
||||
|
||||
hr = m_WrappedDevice->CreateTexture1D(&desc, NULL, &rtTex);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target texture to downcast texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
|
||||
rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE1D;
|
||||
rtvDesc.Format = desc.Format;
|
||||
rtvDesc.Texture1D.MipSlice = mip;
|
||||
|
||||
ID3D11RenderTargetView *wrappedrtv = NULL;
|
||||
hr = m_WrappedDevice->CreateRenderTargetView(rtTex, &rtvDesc, &wrappedrtv);
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target rtv to downcast texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
SAFE_RELEASE(rtTex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ID3D11RenderTargetView *rtv = UNWRAP(WrappedID3D11RenderTargetView, wrappedrtv);
|
||||
|
||||
m_pImmediateContext->OMSetRenderTargets(1, &rtv, NULL);
|
||||
float color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
m_pImmediateContext->ClearRenderTargetView(rtv, color);
|
||||
|
||||
D3D11_VIEWPORT viewport = { 0, 0, (float)(desc.Width>>mip), 1.0f, 0.0f, 1.0f };
|
||||
|
||||
int oldW = GetWidth(), oldH = GetHeight();
|
||||
SetOutputDimensions(desc.Width, 1);
|
||||
m_pImmediateContext->RSSetViewports(1, &viewport);
|
||||
|
||||
{
|
||||
TextureDisplay texDisplay;
|
||||
|
||||
texDisplay.Red = texDisplay.Green = texDisplay.Blue = texDisplay.Alpha = true;
|
||||
texDisplay.HDRMul = -1.0f;
|
||||
texDisplay.linearDisplayAsGamma = true;
|
||||
texDisplay.FlipY = false;
|
||||
texDisplay.mip = mip;
|
||||
texDisplay.sampleIdx = 0;
|
||||
texDisplay.CustomShader = ResourceId();
|
||||
texDisplay.sliceFace = arrayIdx;
|
||||
texDisplay.rangemin = blackPoint;
|
||||
texDisplay.rangemax = whitePoint;
|
||||
texDisplay.scale = 1.0f;
|
||||
texDisplay.texid = id;
|
||||
texDisplay.rawoutput = true;
|
||||
texDisplay.offx = 0;
|
||||
texDisplay.offy = 0;
|
||||
|
||||
RenderTexture(texDisplay);
|
||||
}
|
||||
|
||||
SetOutputDimensions(oldW, oldH);
|
||||
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture1D, d), UNWRAP(WrappedID3D11Texture1D, rtTex));
|
||||
SAFE_RELEASE(rtTex);
|
||||
|
||||
SAFE_RELEASE(wrappedrtv);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture1D, d), wrapTex->GetReal());
|
||||
}
|
||||
}
|
||||
else if(WrappedID3D11Texture2D::m_TextureList.find(id) != WrappedID3D11Texture2D::m_TextureList.end())
|
||||
{
|
||||
@@ -1787,6 +1872,12 @@ byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32
|
||||
mips = desc.MipLevels ? desc.MipLevels : CalcNumMips(desc.Width, desc.Height, 1);
|
||||
|
||||
if(mip >= mips || arrayIdx >= desc.ArraySize) return NULL;
|
||||
|
||||
if(forceRGBA8unorm)
|
||||
{
|
||||
desc.Format = IsSRGBFormat(desc.Format) ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.ArraySize = 1;
|
||||
}
|
||||
|
||||
subresource = arrayIdx*mips + mip;
|
||||
|
||||
@@ -1801,11 +1892,111 @@ byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32
|
||||
}
|
||||
|
||||
bytesize = GetByteSize(desc.Width, desc.Height, 1, desc.Format, mip);
|
||||
|
||||
if(forceRGBA8unorm)
|
||||
{
|
||||
subresource = mip;
|
||||
|
||||
if(wasms)
|
||||
desc.CPUAccessFlags = 0;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
ID3D11Texture2D *rtTex = NULL;
|
||||
|
||||
hr = m_WrappedDevice->CreateTexture2D(&desc, NULL, &rtTex);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target texture to downcast texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
|
||||
rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D;
|
||||
rtvDesc.Format = desc.Format;
|
||||
rtvDesc.Texture2D.MipSlice = mip;
|
||||
|
||||
ID3D11RenderTargetView *wrappedrtv = NULL;
|
||||
hr = m_WrappedDevice->CreateRenderTargetView(rtTex, &rtvDesc, &wrappedrtv);
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target rtv to downcast texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
SAFE_RELEASE(rtTex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ID3D11RenderTargetView *rtv = UNWRAP(WrappedID3D11RenderTargetView, wrappedrtv);
|
||||
|
||||
m_pImmediateContext->OMSetRenderTargets(1, &rtv, NULL);
|
||||
float color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
|
||||
m_pImmediateContext->ClearRenderTargetView(rtv, color);
|
||||
|
||||
D3D11_VIEWPORT viewport = { 0, 0, (float)(desc.Width>>mip), (float)(desc.Height>>mip), 0.0f, 1.0f };
|
||||
|
||||
int oldW = GetWidth(), oldH = GetHeight();
|
||||
SetOutputDimensions(desc.Width, desc.Height);
|
||||
m_pImmediateContext->RSSetViewports(1, &viewport);
|
||||
|
||||
{
|
||||
TextureDisplay texDisplay;
|
||||
|
||||
texDisplay.Red = texDisplay.Green = texDisplay.Blue = texDisplay.Alpha = true;
|
||||
texDisplay.HDRMul = -1.0f;
|
||||
texDisplay.linearDisplayAsGamma = true;
|
||||
texDisplay.FlipY = false;
|
||||
texDisplay.mip = mip;
|
||||
texDisplay.sampleIdx = 0;
|
||||
texDisplay.CustomShader = ResourceId();
|
||||
texDisplay.sliceFace = arrayIdx;
|
||||
texDisplay.rangemin = blackPoint;
|
||||
texDisplay.rangemax = whitePoint;
|
||||
texDisplay.scale = 1.0f;
|
||||
texDisplay.texid = id;
|
||||
texDisplay.rawoutput = true;
|
||||
texDisplay.offx = 0;
|
||||
texDisplay.offy = 0;
|
||||
|
||||
RenderTexture(texDisplay);
|
||||
}
|
||||
|
||||
SetOutputDimensions(oldW, oldH);
|
||||
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture2D, d), UNWRAP(WrappedID3D11Texture2D, rtTex));
|
||||
SAFE_RELEASE(rtTex);
|
||||
|
||||
SAFE_RELEASE(wrappedrtv);
|
||||
}
|
||||
else if(wasms && resolve)
|
||||
{
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.CPUAccessFlags = 0;
|
||||
|
||||
ID3D11Texture2D *resolveTex = NULL;
|
||||
|
||||
hr = m_WrappedDevice->CreateTexture2D(&desc, NULL, &resolveTex);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target texture to resolve texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
m_pImmediateContext->ResolveSubresource(UNWRAP(WrappedID3D11Texture2D, resolveTex), arrayIdx, wrapTex->GetReal(), arrayIdx, desc.Format);
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture2D, d), UNWRAP(WrappedID3D11Texture2D, resolveTex));
|
||||
|
||||
SAFE_RELEASE(resolveTex);
|
||||
}
|
||||
else if(wasms)
|
||||
{
|
||||
CopyTex2DMSToArray(UNWRAP(WrappedID3D11Texture2D, d), wrapTex->GetReal());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture2D, d), wrapTex->GetReal());
|
||||
}
|
||||
}
|
||||
else if(WrappedID3D11Texture3D::m_TextureList.find(id) != WrappedID3D11Texture3D::m_TextureList.end())
|
||||
{
|
||||
@@ -1824,6 +2015,9 @@ byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32
|
||||
mips = desc.MipLevels ? desc.MipLevels : CalcNumMips(desc.Width, desc.Height, desc.Depth);
|
||||
|
||||
if(mip >= mips) return NULL;
|
||||
|
||||
if(forceRGBA8unorm)
|
||||
desc.Format = IsSRGBFormat(desc.Format) ? DXGI_FORMAT_R8G8B8A8_UNORM_SRGB : DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
subresource = mip;
|
||||
|
||||
@@ -1838,8 +2032,92 @@ byte *D3D11DebugManager::GetTextureData(ResourceId id, uint32_t arrayIdx, uint32
|
||||
}
|
||||
|
||||
bytesize = GetByteSize(desc.Width, desc.Height, desc.Depth, desc.Format, mip);
|
||||
|
||||
if(forceRGBA8unorm)
|
||||
{
|
||||
subresource = mip;
|
||||
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture3D, d), wrapTex->GetReal());
|
||||
desc.CPUAccessFlags = 0;
|
||||
desc.Usage = D3D11_USAGE_DEFAULT;
|
||||
desc.BindFlags = D3D11_BIND_RENDER_TARGET;
|
||||
|
||||
ID3D11Texture3D *rtTex = NULL;
|
||||
|
||||
hr = m_WrappedDevice->CreateTexture3D(&desc, NULL, &rtTex);
|
||||
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target texture to downcast texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
D3D11_RENDER_TARGET_VIEW_DESC rtvDesc;
|
||||
rtvDesc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE3D;
|
||||
rtvDesc.Format = desc.Format;
|
||||
rtvDesc.Texture3D.MipSlice = mip;
|
||||
rtvDesc.Texture3D.FirstWSlice = 0;
|
||||
rtvDesc.Texture3D.WSize = 1;
|
||||
ID3D11RenderTargetView *wrappedrtv = NULL;
|
||||
ID3D11RenderTargetView *rtv = NULL;
|
||||
|
||||
D3D11_VIEWPORT viewport = { 0, 0, (float)(desc.Width>>mip), (float)(desc.Height>>mip), 0.0f, 1.0f };
|
||||
|
||||
int oldW = GetWidth(), oldH = GetHeight();
|
||||
|
||||
for(UINT i=0; i < desc.Depth; i++)
|
||||
{
|
||||
rtvDesc.Texture3D.FirstWSlice = i;
|
||||
hr = m_WrappedDevice->CreateRenderTargetView(rtTex, &rtvDesc, &wrappedrtv);
|
||||
if(FAILED(hr))
|
||||
{
|
||||
RDCERR("Couldn't create target rtv to downcast texture. %08x", hr);
|
||||
SAFE_RELEASE(d);
|
||||
SAFE_RELEASE(rtTex);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rtv = UNWRAP(WrappedID3D11RenderTargetView, wrappedrtv);
|
||||
|
||||
m_pImmediateContext->OMSetRenderTargets(1, &rtv, NULL);
|
||||
float color[4] = {0.0f, 0.5f, 0.0f, 0.0f};
|
||||
m_pImmediateContext->ClearRenderTargetView(rtv, color);
|
||||
|
||||
SetOutputDimensions(desc.Width, desc.Height);
|
||||
m_pImmediateContext->RSSetViewports(1, &viewport);
|
||||
|
||||
TextureDisplay texDisplay;
|
||||
|
||||
texDisplay.Red = texDisplay.Green = texDisplay.Blue = texDisplay.Alpha = true;
|
||||
texDisplay.HDRMul = -1.0f;
|
||||
texDisplay.linearDisplayAsGamma = true;
|
||||
texDisplay.FlipY = false;
|
||||
texDisplay.mip = mip;
|
||||
texDisplay.sampleIdx = 0;
|
||||
texDisplay.CustomShader = ResourceId();
|
||||
texDisplay.sliceFace = i;
|
||||
texDisplay.rangemin = blackPoint;
|
||||
texDisplay.rangemax = whitePoint;
|
||||
texDisplay.scale = 1.0f;
|
||||
texDisplay.texid = id;
|
||||
texDisplay.rawoutput = true;
|
||||
texDisplay.offx = 0;
|
||||
texDisplay.offy = 0;
|
||||
|
||||
RenderTexture(texDisplay);
|
||||
|
||||
SAFE_RELEASE(wrappedrtv);
|
||||
}
|
||||
|
||||
SetOutputDimensions(oldW, oldH);
|
||||
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture3D, d), UNWRAP(WrappedID3D11Texture3D, rtTex));
|
||||
SAFE_RELEASE(rtTex);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_pImmediateContext->CopyResource(UNWRAP(WrappedID3D11Texture3D, d), wrapTex->GetReal());
|
||||
}
|
||||
}
|
||||
|
||||
MapIntercept intercept;
|
||||
|
||||
@@ -114,7 +114,7 @@ class D3D11DebugManager
|
||||
vector<byte> GetBufferData(ID3D11Buffer *buff, uint32_t offset, uint32_t len);
|
||||
vector<byte> GetBufferData(ResourceId buff, uint32_t offset, uint32_t len);
|
||||
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize);
|
||||
|
||||
void FillCBufferVariables(const vector<DXBC::CBufferVariable> &invars, vector<ShaderVariable> &outvars,
|
||||
bool flattenVec4s, const vector<byte> &data);
|
||||
|
||||
@@ -1233,9 +1233,9 @@ vector<byte> D3D11Replay::GetBufferData(ResourceId buff, uint32_t offset, uint32
|
||||
return m_pDevice->GetDebugManager()->GetBufferData(buff, offset, len);
|
||||
}
|
||||
|
||||
byte *D3D11Replay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize)
|
||||
byte *D3D11Replay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize)
|
||||
{
|
||||
return m_pDevice->GetDebugManager()->GetTextureData(tex, arrayIdx, mip, dataSize);
|
||||
return m_pDevice->GetDebugManager()->GetTextureData(tex, arrayIdx, mip, resolve, forceRGBA8unorm, blackPoint, whitePoint, dataSize);
|
||||
}
|
||||
|
||||
void D3D11Replay::ReplaceResource(ResourceId from, ResourceId to)
|
||||
@@ -1253,11 +1253,6 @@ void D3D11Replay::TimeDrawcalls(rdctype::array<FetchDrawcall> &arr)
|
||||
return m_pDevice->GetDebugManager()->TimeDrawcalls(arr);
|
||||
}
|
||||
|
||||
bool D3D11Replay::SaveTexture(ResourceId tex, uint32_t saveMip, wstring path)
|
||||
{
|
||||
return m_pDevice->GetDebugManager()->SaveTexture(tex, saveMip, path);
|
||||
}
|
||||
|
||||
void D3D11Replay::RenderMesh(uint32_t frameID, const vector<uint32_t> &events, MeshDisplay cfg)
|
||||
{
|
||||
return m_pDevice->GetDebugManager()->RenderMesh(frameID, events, cfg);
|
||||
|
||||
@@ -88,7 +88,7 @@ class D3D11Replay : public IReplayDriver
|
||||
PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage);
|
||||
|
||||
vector<byte> GetBufferData(ResourceId buff, uint32_t offset, uint32_t len);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize);
|
||||
|
||||
void BuildTargetShader(string source, string entry, const uint32_t compileFlags, ShaderStageType type, ResourceId *id, string *errors);
|
||||
void ReplaceResource(ResourceId from, ResourceId to);
|
||||
@@ -96,8 +96,6 @@ class D3D11Replay : public IReplayDriver
|
||||
|
||||
void TimeDrawcalls(rdctype::array<FetchDrawcall> &arr);
|
||||
|
||||
bool SaveTexture(ResourceId tex, uint32_t saveMip, wstring path);
|
||||
|
||||
ResourceId CreateProxyTexture(FetchTexture templateTex);
|
||||
void SetProxyTextureData(ResourceId texid, uint32_t arrayIdx, uint32_t mip, byte *data, size_t dataSize);
|
||||
|
||||
|
||||
@@ -1389,7 +1389,7 @@ PostVSMeshData GLReplay::GetPostVSBuffers(uint32_t frameID, uint32_t eventID, Me
|
||||
return ret;
|
||||
}
|
||||
|
||||
byte *GLReplay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize)
|
||||
byte *GLReplay::GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize)
|
||||
{
|
||||
RDCUNIMPLEMENTED("GetTextureData");
|
||||
return NULL;
|
||||
@@ -1410,12 +1410,6 @@ void GLReplay::TimeDrawcalls(rdctype::array<FetchDrawcall> &arr)
|
||||
RDCUNIMPLEMENTED("TimeDrawcalls");
|
||||
}
|
||||
|
||||
bool GLReplay::SaveTexture(ResourceId tex, uint32_t saveMip, wstring path)
|
||||
{
|
||||
RDCUNIMPLEMENTED("SaveTexture");
|
||||
return false;
|
||||
}
|
||||
|
||||
void GLReplay::BuildTargetShader(string source, string entry, const uint32_t compileFlags, ShaderStageType type, ResourceId *id, string *errors)
|
||||
{
|
||||
RDCUNIMPLEMENTED("BuildTargetShader");
|
||||
|
||||
@@ -88,15 +88,13 @@ class GLReplay : public IReplayDriver
|
||||
PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage);
|
||||
|
||||
vector<byte> GetBufferData(ResourceId buff, uint32_t offset, uint32_t len);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize);
|
||||
byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize);
|
||||
|
||||
void ReplaceResource(ResourceId from, ResourceId to);
|
||||
void RemoveReplacement(ResourceId id);
|
||||
|
||||
void TimeDrawcalls(rdctype::array<FetchDrawcall> &arr);
|
||||
|
||||
bool SaveTexture(ResourceId tex, uint32_t saveMip, wstring path);
|
||||
|
||||
void RenderMesh(uint32_t frameID, const vector<uint32_t> &events, MeshDisplay cfg);
|
||||
|
||||
void BuildTargetShader(string source, string entry, const uint32_t compileFlags, ShaderStageType type, ResourceId *id, string *errors);
|
||||
|
||||
@@ -82,7 +82,7 @@ class IRemoteDriver
|
||||
virtual PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage) = 0;
|
||||
|
||||
virtual vector<byte> GetBufferData(ResourceId buff, uint32_t offset, uint32_t len) = 0;
|
||||
virtual byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, size_t &dataSize) = 0;
|
||||
virtual byte *GetTextureData(ResourceId tex, uint32_t arrayIdx, uint32_t mip, bool resolve, bool forceRGBA8unorm, float blackPoint, float whitePoint, size_t &dataSize) = 0;
|
||||
|
||||
virtual void BuildTargetShader(string source, string entry, const uint32_t compileFlags, ShaderStageType type, ResourceId *id, string *errors) = 0;
|
||||
virtual void ReplaceResource(ResourceId from, ResourceId to) = 0;
|
||||
@@ -128,8 +128,6 @@ class IReplayDriver : public IRemoteDriver
|
||||
virtual ResourceId CreateProxyTexture(FetchTexture templateTex) = 0;
|
||||
virtual void SetProxyTextureData(ResourceId texid, uint32_t arrayIdx, uint32_t mip, byte *data, size_t dataSize) = 0;
|
||||
|
||||
virtual bool SaveTexture(ResourceId tex, uint32_t saveMip, wstring path) = 0;
|
||||
|
||||
virtual void RenderMesh(uint32_t frameID, const vector<uint32_t> &events, MeshDisplay cfg) = 0;
|
||||
virtual bool RenderTexture(TextureDisplay cfg) = 0;
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <time.h>
|
||||
|
||||
#include "common/string_utils.h"
|
||||
#include "maths/formatpacking.h"
|
||||
#include "os/os_specific.h"
|
||||
|
||||
#include "serialise/serialiser.h"
|
||||
@@ -39,6 +40,97 @@
|
||||
#include "3rdparty/stb/stb_image_write.h"
|
||||
#include "common/dds_readwrite.h"
|
||||
|
||||
static inline 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)
|
||||
{
|
||||
return float(*u32);
|
||||
}
|
||||
else if(fmt.compType == eCompType_SInt)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return float(*u16);
|
||||
}
|
||||
else if(fmt.compType == eCompType_SInt)
|
||||
{
|
||||
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)
|
||||
{
|
||||
return float(*u8);
|
||||
}
|
||||
else if(fmt.compType == eCompType_SInt)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
ReplayRenderer::ReplayRenderer()
|
||||
{
|
||||
m_pDevice = NULL;
|
||||
@@ -305,9 +397,622 @@ bool ReplayRenderer::GetBufferData(ResourceId buff, uint32_t offset, uint32_t le
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReplayRenderer::SaveTexture(ResourceId tex, uint32_t saveMip, const wchar_t *path)
|
||||
bool ReplayRenderer::SaveTexture(const TextureSave &saveData, const wchar_t *path)
|
||||
{
|
||||
return m_pDevice->SaveTexture(m_pDevice->GetLiveID(tex), saveMip, 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;
|
||||
}
|
||||
|
||||
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 = 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;
|
||||
|
||||
// for DDS don't downcast, for non-HDR always downcast if we're not already RGBA8 unorm
|
||||
// for HDR 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 &&
|
||||
(td.format.compByteWidth != 1 || td.format.compType != eCompType_UNorm)
|
||||
) ||
|
||||
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_R10G10B10A2:
|
||||
case eSpecial_R9G9B9E5:
|
||||
case eSpecial_R11G11B10:
|
||||
case eSpecial_D24S8:
|
||||
case eSpecial_B8G8R8A8:
|
||||
bytesPerPixel = 4;
|
||||
break;
|
||||
case eSpecial_B5G6R5:
|
||||
case eSpecial_B5G5R5A1:
|
||||
case eSpecial_B4G4R4A4:
|
||||
bytesPerPixel = 2;
|
||||
break;
|
||||
case eSpecial_D32S8:
|
||||
bytesPerPixel = 5;
|
||||
break;
|
||||
case eSpecial_YUV:
|
||||
RDCERR("Unsupported file save format");
|
||||
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, 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);
|
||||
|
||||
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 += (td.depth-1);
|
||||
|
||||
byte *b = bytes;
|
||||
|
||||
// add each depth slice as a separate subdata
|
||||
for(uint32_t d=0; d < td.depth; d++)
|
||||
{
|
||||
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 % 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 = i % sd.slice.sliceGridWidth;
|
||||
uint32_t gridy = 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;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
delete[] subdata[0];
|
||||
|
||||
subdata[0] = rg0;
|
||||
|
||||
numComps = 3;
|
||||
rowPitch = td.width * 2;
|
||||
}
|
||||
|
||||
FILE *f = FileIO::fopen(path, L"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_file(f, td.width, td.height, numComps, subdata[0]);
|
||||
success = (ret != 0);
|
||||
}
|
||||
else if(sd.destType == eFileType_PNG)
|
||||
{
|
||||
int ret = stbi_write_png_to_file(f, td.width, td.height, td.format.compCount, subdata[0], rowPitch);
|
||||
success = (ret != 0);
|
||||
}
|
||||
else if(sd.destType == eFileType_TGA)
|
||||
{
|
||||
int ret = stbi_write_tga_to_file(f, td.width, td.height, td.format.compCount, 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;
|
||||
|
||||
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)
|
||||
{
|
||||
float *fldata = new float[td.width*td.height*3];
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
|
||||
srcData += td.format.compCount * td.format.compByteWidth;
|
||||
}
|
||||
|
||||
fldata[(y*td.width + x) * 3 + 0] = r;
|
||||
fldata[(y*td.width + x) * 3 + 1] = g;
|
||||
fldata[(y*td.width + x) * 3 + 2] = b;
|
||||
}
|
||||
}
|
||||
|
||||
int ret = stbi_write_hdr_to_file(f, td.width, td.height, 3, fldata);
|
||||
success = (ret != 0);
|
||||
|
||||
delete[] fldata;
|
||||
}
|
||||
|
||||
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, rdctype::array<PixelModification> *history)
|
||||
@@ -770,8 +1475,8 @@ extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_GetUsage(ReplayRendere
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_GetCBufferVariableContents(ReplayRenderer *rend, ResourceId shader, uint32_t cbufslot, ResourceId buffer, uint32_t offs, rdctype::array<ShaderVariable> *vars)
|
||||
{ return rend->GetCBufferVariableContents(shader, cbufslot, buffer, offs, vars); }
|
||||
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_SaveTexture(ReplayRenderer *rend, ResourceId texID, uint32_t mip, const wchar_t *path)
|
||||
{ return rend->SaveTexture(texID, mip, path); }
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_SaveTexture(ReplayRenderer *rend, const TextureSave &saveData, const wchar_t *path)
|
||||
{ return rend->SaveTexture(saveData, path); }
|
||||
|
||||
extern "C" RENDERDOC_API bool RENDERDOC_CC ReplayRenderer_GetPostVSData(ReplayRenderer *rend, MeshDataStage stage, PostVSMeshData *data)
|
||||
{ return rend->GetPostVSData(stage, data); }
|
||||
|
||||
@@ -174,7 +174,7 @@ struct ReplayRenderer
|
||||
|
||||
bool GetBufferData(ResourceId buff, uint32_t offset, uint32_t len, rdctype::array<byte> *data);
|
||||
|
||||
bool SaveTexture(ResourceId tex, uint32_t saveMip, const wchar_t *path);
|
||||
bool SaveTexture(const TextureSave &saveData, const wchar_t *path);
|
||||
|
||||
bool GetCBufferVariableContents(ResourceId shader, uint32_t cbufslot, ResourceId buffer, uint32_t offs, rdctype::array<ShaderVariable> *vars);
|
||||
|
||||
|
||||
@@ -128,6 +128,23 @@ namespace renderdoc
|
||||
QuadOverdrawDraw,
|
||||
};
|
||||
|
||||
public enum FileType
|
||||
{
|
||||
DDS,
|
||||
PNG,
|
||||
JPG,
|
||||
BMP,
|
||||
TGA,
|
||||
HDR,
|
||||
};
|
||||
|
||||
public enum AlphaMapping
|
||||
{
|
||||
Discard,
|
||||
BlendToColour,
|
||||
BlendToCheckerboard,
|
||||
};
|
||||
|
||||
public enum SpecialFormat
|
||||
{
|
||||
Unknown = 0,
|
||||
|
||||
@@ -399,6 +399,55 @@ namespace renderdoc
|
||||
public TextureDisplayOverlay overlay = TextureDisplayOverlay.None;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class TextureSave
|
||||
{
|
||||
public ResourceId id = ResourceId.Null;
|
||||
|
||||
public FileType destType = FileType.DDS;
|
||||
|
||||
public Int32 mip = -1;
|
||||
|
||||
public struct ComponentMapping
|
||||
{
|
||||
public float blackPoint;
|
||||
public float whitePoint;
|
||||
};
|
||||
|
||||
[CustomMarshalAs(CustomUnmanagedType.CustomClass)]
|
||||
public ComponentMapping comp = new ComponentMapping();
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SampleMapping
|
||||
{
|
||||
public bool mapToArray;
|
||||
|
||||
public UInt32 sampleIndex;
|
||||
};
|
||||
[CustomMarshalAs(CustomUnmanagedType.CustomClass)]
|
||||
public SampleMapping sample = new SampleMapping();
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SliceMapping
|
||||
{
|
||||
public Int32 sliceIndex;
|
||||
|
||||
public bool slicesAsGrid;
|
||||
|
||||
public Int32 sliceGridWidth;
|
||||
|
||||
public bool cubeCruciform;
|
||||
};
|
||||
[CustomMarshalAs(CustomUnmanagedType.CustomClass)]
|
||||
public SliceMapping slice = new SliceMapping();
|
||||
|
||||
public AlphaMapping alpha = AlphaMapping.Discard;
|
||||
public FloatVector alphaCol = new FloatVector();
|
||||
public FloatVector alphaColSecondary = new FloatVector();
|
||||
|
||||
public int jpegQuality = 90;
|
||||
};
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public class APIProperties
|
||||
{
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace renderdoc
|
||||
private static extern bool ReplayRenderer_GetCBufferVariableContents(IntPtr real, ResourceId shader, UInt32 cbufslot, ResourceId buffer, UInt32 offs, IntPtr outvars);
|
||||
|
||||
[DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool ReplayRenderer_SaveTexture(IntPtr real, ResourceId texID, UInt32 mip, string path);
|
||||
private static extern bool ReplayRenderer_SaveTexture(IntPtr real, TextureSave saveData, string path);
|
||||
|
||||
[DllImport("renderdoc.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl)]
|
||||
private static extern bool ReplayRenderer_GetPostVSData(IntPtr real, MeshDataStage stage, IntPtr outdata);
|
||||
@@ -586,8 +586,8 @@ namespace renderdoc
|
||||
return ret;
|
||||
}
|
||||
|
||||
public bool SaveTexture(ResourceId texID, UInt32 mip, string path)
|
||||
{ return ReplayRenderer_SaveTexture(m_Real, texID, mip, path); }
|
||||
public bool SaveTexture(TextureSave saveData, string path)
|
||||
{ return ReplayRenderer_SaveTexture(m_Real, saveData, path); }
|
||||
|
||||
public PostVSMeshData GetPostVSData(MeshDataStage stage)
|
||||
{
|
||||
|
||||
@@ -2702,13 +2702,46 @@ namespace renderdocui.Windows
|
||||
|
||||
private void saveTex_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (saveTextureDialog.ShowDialog() == DialogResult.OK)
|
||||
//if (saveTextureDialog.ShowDialog() == DialogResult.OK)
|
||||
{
|
||||
TextureSave sd = new TextureSave();
|
||||
|
||||
sd.destType = FileType.DDS;
|
||||
saveTextureDialog.FileName = "T:/tmp/a.dds";
|
||||
|
||||
sd.id = m_TexDisplay.texid;
|
||||
|
||||
sd.slice.sliceIndex = (int)m_TexDisplay.sliceFace;
|
||||
if (sd.destType == FileType.DDS)
|
||||
sd.slice.sliceIndex = -1;
|
||||
|
||||
sd.mip = (int)m_TexDisplay.mip;
|
||||
if (sd.destType == FileType.DDS)
|
||||
sd.mip = -1;
|
||||
|
||||
if (sd.destType == FileType.DDS)
|
||||
{
|
||||
sd.sample.mapToArray = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
sd.sample.mapToArray = false;
|
||||
sd.sample.sampleIndex = m_TexDisplay.sampleIdx;
|
||||
}
|
||||
|
||||
sd.comp.blackPoint = m_TexDisplay.rangemin;
|
||||
sd.comp.whitePoint = m_TexDisplay.rangemax;
|
||||
|
||||
sd.alphaCol = m_TexDisplay.lightBackgroundColour;
|
||||
sd.alphaColSecondary = m_TexDisplay.darkBackgroundColour;
|
||||
|
||||
sd.alpha = m_TexDisplay.Alpha ? AlphaMapping.BlendToCheckerboard : AlphaMapping.Discard;
|
||||
|
||||
bool ret = false;
|
||||
|
||||
m_Core.Renderer.Invoke((ReplayRenderer r) =>
|
||||
{
|
||||
ret = r.SaveTexture(m_TexDisplay.texid, m_TexDisplay.mip, saveTextureDialog.FileName);
|
||||
ret = r.SaveTexture(sd, saveTextureDialog.FileName);
|
||||
});
|
||||
|
||||
if(!ret)
|
||||
|
||||
Reference in New Issue
Block a user