Files
renderdoc/renderdoc/driver/vulkan/vk_rendertexture.cpp
T

610 lines
19 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2023 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "core/settings.h"
#include "maths/formatpacking.h"
#include "maths/matrix.h"
#include "vk_core.h"
#include "vk_debug.h"
#include "vk_replay.h"
#define VULKAN 1
#include "data/glsl/glsl_ubos_cpp.h"
RDOC_EXTERN_CONFIG(bool, Vulkan_Debug_SingleSubmitFlushing);
void VulkanReplay::CreateTexImageView(VkImage liveIm, const VulkanCreationInfo::Image &iminfo,
CompType typeCast, TextureDisplayViews &views)
{
VkDevice dev = m_pDriver->GetDev();
if(views.typeCast != typeCast)
{
// if the type hint has changed, recreate the image views
// flush any pending commands that might use the old views
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
for(size_t i = 0; i < ARRAY_COUNT(views.views); i++)
{
m_pDriver->vkDestroyImageView(dev, views.views[i], NULL);
views.views[i] = VK_NULL_HANDLE;
}
}
views.typeCast = typeCast;
VkFormat fmt = views.castedFormat = GetViewCastedFormat(iminfo.format, typeCast);
// all types have at least views[0] populated, so if it's still there, we can just return
if(views.views[0] != VK_NULL_HANDLE)
return;
VkImageViewCreateInfo viewInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
NULL,
0,
liveIm,
VK_IMAGE_VIEW_TYPE_2D_ARRAY,
fmt,
{VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY},
{
VK_IMAGE_ASPECT_COLOR_BIT, 0, RDCMAX(1U, iminfo.mipLevels), 0,
RDCMAX(1U, iminfo.arrayLayers),
},
};
// for the stencil-only format, the first view is stencil only
if(fmt == VK_FORMAT_S8_UINT)
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
// otherwise for depth or stencil formats, the first view is depth.
else if(IsDepthOrStencilFormat(fmt))
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
if(iminfo.type == VK_IMAGE_TYPE_1D)
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_1D_ARRAY;
else if(iminfo.type == VK_IMAGE_TYPE_3D)
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_3D;
VkResult vkr = VK_SUCCESS;
if(IsYUVFormat(fmt))
{
const uint32_t planeCount = GetYUVPlaneCount(fmt);
for(uint32_t i = 0; i < planeCount; i++)
{
viewInfo.format = GetYUVViewPlaneFormat(fmt, i);
if(planeCount > 1)
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_PLANE_0_BIT << i;
// create as wrapped
vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &views.views[i]);
CheckVkResult(vkr);
}
}
else
{
// create first view
vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &views.views[0]);
CheckVkResult(vkr);
NameVulkanObject(views.views[0], StringFormat::Fmt("CreateTexImageView view 0 %s",
ToStr(GetResID(liveIm)).c_str()));
// for depth-stencil images, create a second view for stencil only
if(IsDepthAndStencilFormat(fmt))
{
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
vkr = m_pDriver->vkCreateImageView(dev, &viewInfo, NULL, &views.views[1]);
CheckVkResult(vkr);
NameVulkanObject(views.views[1], StringFormat::Fmt("CreateTexImageView view 1 %s",
ToStr(GetResID(liveIm)).c_str()));
}
}
}
bool VulkanReplay::RenderTexture(TextureDisplay cfg)
{
auto it = m_OutputWindows.find(m_ActiveWinID);
if(it == m_OutputWindows.end())
{
RDCERR("output window not bound");
return false;
}
OutputWindow &outw = it->second;
// if the swapchain failed to create, do nothing. We will try to recreate it
// again in CheckResizeOutputWindow (once per render 'frame')
if(outw.m_WindowSystem != WindowingSystem::Headless && outw.swap == VK_NULL_HANDLE)
return false;
VkRenderPassBeginInfo rpbegin = {
VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
NULL,
Unwrap(outw.rp),
Unwrap(outw.fb),
{{
0, 0,
},
{m_DebugWidth, m_DebugHeight}},
0,
NULL,
};
LockedConstImageStateRef imageState = m_pDriver->FindConstImageState(cfg.resourceId);
if(!imageState)
{
RDCWARN("Could not find image info for image %s", ToStr(cfg.resourceId).c_str());
return false;
}
if(!imageState->isMemoryBound)
return false;
return RenderTextureInternal(cfg, *imageState, rpbegin,
eTexDisplay_MipShift | eTexDisplay_BlendAlpha);
}
bool VulkanReplay::RenderTextureInternal(TextureDisplay cfg, const ImageState &imageState,
VkRenderPassBeginInfo rpbegin, int flags)
{
const bool blendAlpha = (flags & eTexDisplay_BlendAlpha) != 0;
const bool mipShift = (flags & eTexDisplay_MipShift) != 0;
const bool f16render = (flags & eTexDisplay_16Render) != 0;
const bool greenonly = (flags & eTexDisplay_GreenOnly) != 0;
const bool f32render = (flags & eTexDisplay_32Render) != 0;
VkDevice dev = m_pDriver->GetDev();
const VkDevDispatchTable *vt = ObjDisp(dev);
const ImageInfo &imageInfo = imageState.GetImageInfo();
VulkanCreationInfo::Image &iminfo = m_pDriver->m_CreationInfo.m_Image[cfg.resourceId];
TextureDisplayViews &texviews = m_TexRender.TextureViews[cfg.resourceId];
VkImage liveIm = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImage>(cfg.resourceId);
CreateTexImageView(liveIm, iminfo, cfg.typeCast, texviews);
int displayformat = 0;
uint32_t descSetBinding = 0;
if(IsUIntFormat(texviews.castedFormat))
{
descSetBinding = 10;
displayformat |= TEXDISPLAY_UINT_TEX;
}
else if(IsSIntFormat(texviews.castedFormat))
{
descSetBinding = 15;
displayformat |= TEXDISPLAY_SINT_TEX;
}
else
{
descSetBinding = 5;
}
// by default we use view 0
int viewIndex = 0;
// if we're displaying the stencil, set up for stencil display
if(imageInfo.format == VK_FORMAT_S8_UINT ||
(IsStencilFormat(imageInfo.format) && !cfg.red && cfg.green))
{
descSetBinding = 10;
displayformat |= TEXDISPLAY_UINT_TEX;
// for stencil we use view 1 as long as it's a depth-stencil texture
if(IsDepthAndStencilFormat(imageInfo.format))
viewIndex = 1;
// rescale the range so that stencil seems to fit to 0-1
cfg.rangeMin *= 255.0f;
cfg.rangeMax *= 255.0f;
// shuffle the channel selection, since stencil comes back in red
cfg.red = true;
cfg.green = false;
}
VkImageView liveImView = texviews.views[viewIndex];
RDCASSERT(liveImView != VK_NULL_HANDLE);
uint32_t uboOffs = 0;
TexDisplayUBOData *data = (TexDisplayUBOData *)m_TexRender.UBO.Map(&uboOffs);
if(!data)
return false;
data->Padding = 0;
float x = cfg.xOffset;
float y = cfg.yOffset;
data->Position.x = x;
data->Position.y = y;
data->HDRMul = cfg.hdrMultiplier;
data->DecodeYUV = cfg.decodeYUV ? 1 : 0;
Vec4u YUVDownsampleRate = {};
Vec4u YUVAChannels = {};
GetYUVShaderParameters(texviews.castedFormat, YUVDownsampleRate, YUVAChannels);
data->YUVDownsampleRate = YUVDownsampleRate;
data->YUVAChannels = YUVAChannels;
int32_t tex_x = iminfo.extent.width;
int32_t tex_y = iminfo.extent.height;
int32_t tex_z = iminfo.extent.depth;
if(cfg.scale <= 0.0f)
{
float xscale = float(m_DebugWidth) / float(tex_x);
float yscale = float(m_DebugHeight) / float(tex_y);
// update cfg.scale for use below
float scale = cfg.scale = RDCMIN(xscale, yscale);
if(yscale > xscale)
{
data->Position.x = 0;
data->Position.y = (float(m_DebugHeight) - (tex_y * scale)) * 0.5f;
}
else
{
data->Position.y = 0;
data->Position.x = (float(m_DebugWidth) - (tex_x * scale)) * 0.5f;
}
}
data->Channels.x = cfg.red ? 1.0f : 0.0f;
data->Channels.y = cfg.green ? 1.0f : 0.0f;
data->Channels.z = cfg.blue ? 1.0f : 0.0f;
data->Channels.w = cfg.alpha ? 1.0f : 0.0f;
if(cfg.rangeMax <= cfg.rangeMin)
cfg.rangeMax += 0.00001f;
data->RangeMinimum = cfg.rangeMin;
data->InverseRangeSize = 1.0f / (cfg.rangeMax - cfg.rangeMin);
data->FlipY = cfg.flipY ? 1 : 0;
const bool linearSample = cfg.subresource.mip == 0 && cfg.scale < 1.0f &&
(displayformat & (TEXDISPLAY_UINT_TEX | TEXDISPLAY_SINT_TEX)) == 0;
data->MipLevel = (int)cfg.subresource.mip;
data->Slice = 0;
if(iminfo.type != VK_IMAGE_TYPE_3D)
{
uint32_t numSlices = RDCMAX((uint32_t)iminfo.arrayLayers, 1U);
uint32_t sliceFace = RDCCLAMP(cfg.subresource.slice, 0U, numSlices - 1);
data->Slice = (float)sliceFace + 0.001f;
}
else
{
float slice = (float)RDCCLAMP(cfg.subresource.slice, 0U, iminfo.extent.depth - 1);
// when sampling linearly, we need to add half a pixel to ensure we only sample the desired
// slice
if(linearSample)
slice += 0.5f;
else
slice += 0.001f;
data->Slice = slice;
}
data->TextureResolutionPS.x = float(RDCMAX(1, tex_x >> cfg.subresource.mip));
data->TextureResolutionPS.y = float(RDCMAX(1, tex_y >> cfg.subresource.mip));
data->TextureResolutionPS.z = float(RDCMAX(1, tex_z >> cfg.subresource.mip));
if(mipShift)
data->MipShift = float(1 << cfg.subresource.mip);
else
data->MipShift = 1.0f;
data->Scale = cfg.scale;
int sampleIdx = (int)RDCCLAMP(cfg.subresource.sample, 0U, (uint32_t)SampleCount(iminfo.samples));
if(cfg.subresource.sample == ~0U)
sampleIdx = -SampleCount(iminfo.samples);
data->SampleIdx = sampleIdx;
data->OutputRes.x = (float)m_DebugWidth;
data->OutputRes.y = (float)m_DebugHeight;
int textype = 0;
if(iminfo.type == VK_IMAGE_TYPE_1D)
{
textype = RESTYPE_TEX1D;
}
else if(iminfo.type == VK_IMAGE_TYPE_3D)
{
textype = RESTYPE_TEX3D;
}
else if(iminfo.type == VK_IMAGE_TYPE_2D)
{
textype = RESTYPE_TEX2D;
if(iminfo.samples != VK_SAMPLE_COUNT_1_BIT)
textype = RESTYPE_TEX2DMS;
}
displayformat |= textype;
descSetBinding += textype;
if(!IsSRGBFormat(texviews.castedFormat) && cfg.linearDisplayAsGamma)
displayformat |= TEXDISPLAY_GAMMA_CURVE;
if(cfg.overlay == DebugOverlay::NaN)
displayformat |= TEXDISPLAY_NANS;
if(cfg.overlay == DebugOverlay::Clipping)
displayformat |= TEXDISPLAY_CLIPPING;
data->OutputDisplayFormat = displayformat;
data->RawOutput = cfg.rawOutput ? 1 : 0;
if(cfg.customShaderId != ResourceId())
{
// must match struct declared in user shader (see documentation / Shader Viewer window helper
// menus)
RD_CustomShader_UBO_Type *customData = (RD_CustomShader_UBO_Type *)data;
customData->TexDim.x = iminfo.extent.width;
customData->TexDim.y = iminfo.extent.height;
customData->TexDim.z = iminfo.type == VK_IMAGE_TYPE_3D ? iminfo.extent.depth : iminfo.arrayLayers;
customData->TexDim.w = iminfo.mipLevels;
customData->SelectedMip = cfg.subresource.mip;
customData->SelectedSliceFace = cfg.subresource.slice;
customData->SelectedSample = sampleIdx;
customData->TextureType = (uint32_t)textype;
customData->YUVDownsampleRate = YUVDownsampleRate;
customData->YUVAChannels = YUVAChannels;
customData->SelectedRange.x = cfg.rangeMin;
customData->SelectedRange.y = cfg.rangeMax;
}
m_TexRender.UBO.Unmap();
HeatmapData heatmapData = {};
{
if(cfg.overlay == DebugOverlay::QuadOverdrawDraw || cfg.overlay == DebugOverlay::QuadOverdrawPass)
{
heatmapData.HeatmapMode = HEATMAP_LINEAR;
}
else if(cfg.overlay == DebugOverlay::TriangleSizeDraw ||
cfg.overlay == DebugOverlay::TriangleSizePass)
{
heatmapData.HeatmapMode = HEATMAP_TRISIZE;
}
if(heatmapData.HeatmapMode)
{
memcpy(heatmapData.ColorRamp, colorRamp, sizeof(colorRamp));
RDCCOMPILE_ASSERT(sizeof(heatmapData.ColorRamp) == sizeof(colorRamp),
"C++ color ramp array is not the same size as the shader array");
}
}
uint32_t heatUboOffs = 0;
{
HeatmapData *ptr = (HeatmapData *)m_TexRender.HeatmapUBO.Map(&heatUboOffs);
if(!ptr)
return false;
memcpy(ptr, &heatmapData, sizeof(HeatmapData));
m_TexRender.HeatmapUBO.Unmap();
}
VkDescriptorImageInfo imdesc = {0};
imdesc.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
imdesc.imageView = Unwrap(liveImView);
imdesc.sampler = Unwrap(m_General.PointSampler);
if(linearSample)
imdesc.sampler = Unwrap(m_TexRender.LinearSampler);
VkDescriptorImageInfo altimdesc[2] = {};
for(uint32_t i = 1; i < GetYUVPlaneCount(texviews.castedFormat); i++)
{
RDCASSERT(texviews.views[i] != VK_NULL_HANDLE);
altimdesc[i - 1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
altimdesc[i - 1].imageView = Unwrap(texviews.views[i]);
altimdesc[i - 1].sampler = Unwrap(m_General.PointSampler);
if(linearSample)
altimdesc[i - 1].sampler = Unwrap(m_TexRender.LinearSampler);
}
VkDescriptorSet descset = m_TexRender.GetDescSet();
VkDescriptorBufferInfo ubodesc = {}, heatubodesc = {};
m_TexRender.UBO.FillDescriptor(ubodesc);
m_TexRender.HeatmapUBO.FillDescriptor(heatubodesc);
VkWriteDescriptorSet writeSet[] = {
// sampled view
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descset), descSetBinding, 0, 1,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, &imdesc, NULL, NULL},
// YUV secondary planes (if needed)
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descset), 10, 0,
GetYUVPlaneCount(texviews.castedFormat) - 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
altimdesc, NULL, NULL},
// UBOs
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descset), 0, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &ubodesc, NULL},
{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, NULL, Unwrap(descset), 1, 0, 1,
VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, NULL, &heatubodesc, NULL},
};
rdcarray<VkWriteDescriptorSet> writeSets;
for(size_t i = 0; i < ARRAY_COUNT(writeSet); i++)
{
if(writeSet[i].descriptorCount > 0)
writeSets.push_back(writeSet[i]);
}
for(size_t i = 0; i < ARRAY_COUNT(m_TexRender.DummyWrites); i++)
{
VkWriteDescriptorSet &write = m_TexRender.DummyWrites[i];
// don't write dummy data in the actual slot
if(write.dstBinding == descSetBinding)
continue;
// don't overwrite YUV texture slots if it's a YUV planar format
if(write.dstBinding == 10)
{
if(write.dstArrayElement == 0 && GetYUVPlaneCount(texviews.castedFormat) >= 2)
continue;
if(write.dstArrayElement == 1 && GetYUVPlaneCount(texviews.castedFormat) >= 3)
continue;
}
write.dstSet = Unwrap(descset);
writeSets.push_back(write);
}
vt->UpdateDescriptorSets(Unwrap(dev), (uint32_t)writeSets.size(), &writeSets[0], 0, NULL);
VkCommandBuffer cmd = m_pDriver->GetNextCmd();
if(cmd == VK_NULL_HANDLE)
return false;
VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL,
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT};
vt->BeginCommandBuffer(Unwrap(cmd), &beginInfo);
VkMarkerRegion::Begin("RenderTexture", cmd);
ImageBarrierSequence setupBarriers, cleanupBarriers;
imageState.TempTransition(m_pDriver->GetQueueFamilyIndex(),
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_ACCESS_SHADER_READ_BIT,
setupBarriers, cleanupBarriers, m_pDriver->GetImageTransitionInfo());
m_pDriver->InlineSetupImageBarriers(cmd, setupBarriers);
m_pDriver->SubmitAndFlushImageStateBarriers(setupBarriers);
{
vt->CmdBeginRenderPass(Unwrap(cmd), &rpbegin, VK_SUBPASS_CONTENTS_INLINE);
VkPipeline pipe = greenonly ? m_TexRender.PipelineGreenOnly : m_TexRender.Pipeline;
if(cfg.customShaderId != ResourceId())
{
GetDebugManager()->CreateCustomShaderPipeline(cfg.customShaderId, m_TexRender.PipeLayout);
pipe = GetDebugManager()->GetCustomPipeline();
}
else if(flags & (eTexDisplay_RemapFloat | eTexDisplay_RemapUInt | eTexDisplay_RemapSInt))
{
int i = 0;
if(flags & eTexDisplay_RemapFloat)
i = 0;
else if(flags & eTexDisplay_RemapUInt)
i = 1;
else if(flags & eTexDisplay_RemapSInt)
i = 2;
int f = 0;
if(flags & eTexDisplay_32Render)
f = 2;
else if(flags & eTexDisplay_16Render)
f = 1;
else
f = 0;
bool srgb = (flags & eTexDisplay_RemapSRGB) != 0;
pipe = m_TexRender.RemapPipeline[f][i][(greenonly || srgb) ? 1 : 0];
}
else if(f16render)
{
pipe = greenonly ? m_TexRender.F16PipelineGreenOnly : m_TexRender.F16Pipeline;
}
else if(f32render)
{
pipe = greenonly ? m_TexRender.F32PipelineGreenOnly : m_TexRender.F32Pipeline;
}
else if(!cfg.rawOutput && blendAlpha && cfg.customShaderId == ResourceId())
{
pipe = m_TexRender.BlendPipeline;
}
uint32_t offsets[] = {uboOffs, heatUboOffs};
vt->CmdBindPipeline(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS, Unwrap(pipe));
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_TexRender.PipeLayout), 0, 1, UnwrapPtr(descset), 2, offsets);
VkViewport viewport = {(float)rpbegin.renderArea.offset.x,
(float)rpbegin.renderArea.offset.y,
(float)rpbegin.renderArea.extent.width,
(float)rpbegin.renderArea.extent.height,
0.0f,
1.0f};
vt->CmdSetViewport(Unwrap(cmd), 0, 1, &viewport);
vt->CmdDraw(Unwrap(cmd), 4, 1, 0, 0);
if(m_pDriver->GetDriverInfo().QualcommLeakingUBOOffsets())
{
offsets[0] = offsets[1] = 0;
vt->CmdBindDescriptorSets(Unwrap(cmd), VK_PIPELINE_BIND_POINT_GRAPHICS,
Unwrap(m_TexRender.PipeLayout), 0, 1, UnwrapPtr(descset), 2, offsets);
}
vt->CmdEndRenderPass(Unwrap(cmd));
}
m_pDriver->InlineCleanupImageBarriers(cmd, cleanupBarriers);
VkMarkerRegion::End(cmd);
vt->EndCommandBuffer(Unwrap(cmd));
if(!cleanupBarriers.empty())
{
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
m_pDriver->SubmitAndFlushImageStateBarriers(cleanupBarriers);
}
else if(Vulkan_Debug_SingleSubmitFlushing())
{
m_pDriver->SubmitCmds();
}
return true;
}