diff --git a/renderdoc/api/replay/control_types.h b/renderdoc/api/replay/control_types.h index a71b94c01..3ef69b056 100644 --- a/renderdoc/api/replay/control_types.h +++ b/renderdoc/api/replay/control_types.h @@ -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() {} diff --git a/renderdoc/api/replay/renderdoc_replay.h b/renderdoc/api/replay/renderdoc_replay.h index 1af93c0e3..5a6c1bd90 100644 --- a/renderdoc/api/replay/renderdoc_replay.h +++ b/renderdoc/api/replay/renderdoc_replay.h @@ -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 *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); diff --git a/renderdoc/api/replay/replay_enums.h b/renderdoc/api/replay/replay_enums.h index 833e78eb9..3adb6f601 100644 --- a/renderdoc/api/replay/replay_enums.h +++ b/renderdoc/api/replay/replay_enums.h @@ -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, diff --git a/renderdoc/core/replay_proxy.cpp b/renderdoc/core/replay_proxy.cpp index e4c98d4f2..1f51f7452 100644 --- a/renderdoc/core/replay_proxy.cpp +++ b/renderdoc/core/replay_proxy.cpp @@ -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 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]; diff --git a/renderdoc/core/replay_proxy.h b/renderdoc/core/replay_proxy.h index 99e9f84e9..96fcd8ae2 100644 --- a/renderdoc/core/replay_proxy.h +++ b/renderdoc/core/replay_proxy.h @@ -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 &outvars, const vector &data); vector 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); diff --git a/renderdoc/driver/d3d11/d3d11_analyse.cpp b/renderdoc/driver/d3d11/d3d11_analyse.cpp index 31614f6ca..a5366c7ed 100644 --- a/renderdoc/driver/d3d11/d3d11_analyse.cpp +++ b/renderdoc/driver/d3d11/d3d11_analyse.cpp @@ -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; diff --git a/renderdoc/driver/d3d11/d3d11_debug.h b/renderdoc/driver/d3d11/d3d11_debug.h index 0cf16b758..d1a78c31f 100644 --- a/renderdoc/driver/d3d11/d3d11_debug.h +++ b/renderdoc/driver/d3d11/d3d11_debug.h @@ -114,7 +114,7 @@ class D3D11DebugManager vector GetBufferData(ID3D11Buffer *buff, uint32_t offset, uint32_t len); vector 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 &invars, vector &outvars, bool flattenVec4s, const vector &data); diff --git a/renderdoc/driver/d3d11/d3d11_replay.cpp b/renderdoc/driver/d3d11/d3d11_replay.cpp index cf2ef15a2..406e17ebd 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.cpp +++ b/renderdoc/driver/d3d11/d3d11_replay.cpp @@ -1233,9 +1233,9 @@ vector 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 &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 &events, MeshDisplay cfg) { return m_pDevice->GetDebugManager()->RenderMesh(frameID, events, cfg); diff --git a/renderdoc/driver/d3d11/d3d11_replay.h b/renderdoc/driver/d3d11/d3d11_replay.h index 86b8ef5eb..b36258a5b 100644 --- a/renderdoc/driver/d3d11/d3d11_replay.h +++ b/renderdoc/driver/d3d11/d3d11_replay.h @@ -88,7 +88,7 @@ class D3D11Replay : public IReplayDriver PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage); vector 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 &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); diff --git a/renderdoc/driver/gl/gl_replay.cpp b/renderdoc/driver/gl/gl_replay.cpp index 6fcda2e78..1ba0990ed 100644 --- a/renderdoc/driver/gl/gl_replay.cpp +++ b/renderdoc/driver/gl/gl_replay.cpp @@ -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 &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"); diff --git a/renderdoc/driver/gl/gl_replay.h b/renderdoc/driver/gl/gl_replay.h index 5d143a0da..08f3b0d37 100644 --- a/renderdoc/driver/gl/gl_replay.h +++ b/renderdoc/driver/gl/gl_replay.h @@ -88,15 +88,13 @@ class GLReplay : public IReplayDriver PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage); vector 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 &arr); - bool SaveTexture(ResourceId tex, uint32_t saveMip, wstring path); - void RenderMesh(uint32_t frameID, const vector &events, MeshDisplay cfg); void BuildTargetShader(string source, string entry, const uint32_t compileFlags, ShaderStageType type, ResourceId *id, string *errors); diff --git a/renderdoc/replay/replay_driver.h b/renderdoc/replay/replay_driver.h index 4b6259f3d..8f146a463 100644 --- a/renderdoc/replay/replay_driver.h +++ b/renderdoc/replay/replay_driver.h @@ -82,7 +82,7 @@ class IRemoteDriver virtual PostVSMeshData GetPostVSBuffers(uint32_t frameID, uint32_t eventID, MeshDataStage stage) = 0; virtual vector 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 &events, MeshDisplay cfg) = 0; virtual bool RenderTexture(TextureDisplay cfg) = 0; diff --git a/renderdoc/replay/replay_renderer.cpp b/renderdoc/replay/replay_renderer.cpp index f3e0a2995..fe2fbf7d7 100644 --- a/renderdoc/replay/replay_renderer.cpp +++ b/renderdoc/replay/replay_renderer.cpp @@ -29,6 +29,7 @@ #include #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 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 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 *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 *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); } diff --git a/renderdoc/replay/replay_renderer.h b/renderdoc/replay/replay_renderer.h index 5a4fb4bc2..bc0aa79af 100644 --- a/renderdoc/replay/replay_renderer.h +++ b/renderdoc/replay/replay_renderer.h @@ -174,7 +174,7 @@ struct ReplayRenderer bool GetBufferData(ResourceId buff, uint32_t offset, uint32_t len, rdctype::array *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 *vars); diff --git a/renderdocui/Interop/Enums.cs b/renderdocui/Interop/Enums.cs index 464cbc439..72728d924 100644 --- a/renderdocui/Interop/Enums.cs +++ b/renderdocui/Interop/Enums.cs @@ -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, diff --git a/renderdocui/Interop/FetchInfo.cs b/renderdocui/Interop/FetchInfo.cs index 82e4aa497..886071914 100644 --- a/renderdocui/Interop/FetchInfo.cs +++ b/renderdocui/Interop/FetchInfo.cs @@ -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 { diff --git a/renderdocui/Interop/ReplayRenderer.cs b/renderdocui/Interop/ReplayRenderer.cs index ef75e2c70..19892f85a 100644 --- a/renderdocui/Interop/ReplayRenderer.cs +++ b/renderdocui/Interop/ReplayRenderer.cs @@ -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) { diff --git a/renderdocui/Windows/TextureViewer.cs b/renderdocui/Windows/TextureViewer.cs index 45b0fa54f..aabd93606 100644 --- a/renderdocui/Windows/TextureViewer.cs +++ b/renderdocui/Windows/TextureViewer.cs @@ -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)