Vk Pixel History: per fragment data

- For now only per fragment primitive ID, and colour values, not depth
information.
This commit is contained in:
Aliya Pazylbekova
2020-03-20 16:16:12 +00:00
committed by Baldur Karlsson
parent 35a3c09b6e
commit 83623af8d8
11 changed files with 848 additions and 16 deletions
+1
View File
@@ -385,6 +385,7 @@ set(data
data/glsl/minmaxtile.comp
data/glsl/quadresolve.frag
data/glsl/quadwrite.frag
data/glsl/pixelhistory.frag
data/glsl/texdisplay.frag
data/glsl/texremap.frag
data/glsl/gl_texsample.h
+8
View File
@@ -0,0 +1,8 @@
#include "glsl_globals.h"
IO_LOCATION(0) out vec4 color_out;
void main(void)
{
color_out = intBitsToFloat(gl_PrimitiveID).xxxx;
}
+1
View File
@@ -128,6 +128,7 @@ RESOURCE_glsl_mesh_frag TYPE_EMBED "glsl/mesh.frag"
RESOURCE_glsl_minmaxtile_comp TYPE_EMBED "glsl/minmaxtile.comp"
RESOURCE_glsl_minmaxresult_comp TYPE_EMBED "glsl/minmaxresult.comp"
RESOURCE_glsl_histogram_comp TYPE_EMBED "glsl/histogram.comp"
RESOURCE_glsl_pixelhistory_frag TYPE_EMBED "glsl/pixelhistory.frag"
RESOURCE_glsl_glsl_ubos_h TYPE_EMBED "glsl/glsl_ubos.h"
RESOURCE_glsl_gl_texsample_h TYPE_EMBED "glsl/gl_texsample.h"
RESOURCE_glsl_vk_texsample_h TYPE_EMBED "glsl/vk_texsample.h"
+1
View File
@@ -48,6 +48,7 @@
#define RESOURCE_glsl_gltext_frag 430
#define RESOURCE_glsl_glsl_globals_h 440
#define RESOURCE_glsl_texremap_frag 441
#define RESOURCE_glsl_pixelhistory_frag 442
// Next default values for new objects
//
+7
View File
@@ -1484,6 +1484,13 @@ const VulkanCreationInfo::Image &VulkanDebugManager::GetImageInfo(ResourceId img
return it->second;
}
const VulkanCreationInfo::ImageView &VulkanDebugManager::GetImageViewInfo(ResourceId imgView) const
{
auto it = m_pDriver->m_CreationInfo.m_ImageView.find(imgView);
RDCASSERT(it != m_pDriver->m_CreationInfo.m_ImageView.end());
return it->second;
}
const VulkanCreationInfo::Pipeline &VulkanDebugManager::GetPipelineInfo(ResourceId pipe) const
{
auto it = m_pDriver->m_CreationInfo.m_Pipeline.find(pipe);
+1
View File
@@ -90,6 +90,7 @@ public:
uint32_t slice);
const VulkanCreationInfo::Image &GetImageInfo(ResourceId img) const;
const VulkanCreationInfo::ImageView &GetImageViewInfo(ResourceId imgView) const;
const VulkanCreationInfo::Pipeline &GetPipelineInfo(ResourceId pipe) const;
const VulkanCreationInfo::ShaderModule &GetShaderInfo(ResourceId shader) const;
const VulkanCreationInfo::Framebuffer &GetFramebufferInfo(ResourceId fb) const;
+822 -16
View File
@@ -104,11 +104,19 @@ struct EventInfo
{
PixelHistoryValue premod;
PixelHistoryValue postmod;
PixelHistoryValue shadout;
uint8_t dsWithoutShaderDiscard[8];
uint8_t dsWithShaderDiscard[8];
};
struct PerFragmentInfo
{
// primitive ID is copied from a R32G32B32A32 texture.
int32_t primitiveID;
uint32_t padding[3];
uint32_t shaderOut[4];
uint32_t postMod[4];
};
struct PipelineReplacements
{
VkPipeline fixedShaderStencil;
@@ -118,7 +126,10 @@ struct PipelineReplacements
// PixelHistoryShaderCache manages temporary shaders created for pixel history.
struct PixelHistoryShaderCache
{
PixelHistoryShaderCache(WrappedVulkan *vk) : m_pDriver(vk), m_FixedColFS(VK_NULL_HANDLE) {}
PixelHistoryShaderCache(WrappedVulkan *vk)
: m_pDriver(vk), m_FixedColFS(VK_NULL_HANDLE), m_PrimitiveIDFS(VK_NULL_HANDLE)
{
}
~PixelHistoryShaderCache()
{
for(auto it = m_ShaderReplacements.begin(); it != m_ShaderReplacements.end(); ++it)
@@ -128,6 +139,8 @@ struct PixelHistoryShaderCache
}
if(m_FixedColFS != VK_NULL_HANDLE)
m_pDriver->vkDestroyShaderModule(m_pDriver->GetDev(), m_FixedColFS, NULL);
if(m_PrimitiveIDFS != VK_NULL_HANDLE)
m_pDriver->vkDestroyShaderModule(m_pDriver->GetDev(), m_PrimitiveIDFS, NULL);
}
// Returns a fragment shader module that outputs a fixed color.
@@ -142,6 +155,23 @@ struct PixelHistoryShaderCache
return m_FixedColFS;
}
VkShaderModule GetPrimitiveIdShader()
{
if(m_PrimitiveIDFS == VK_NULL_HANDLE)
{
rdcarray<uint32_t> spv =
*m_pDriver->GetShaderCache()->GetBuiltinBlob(BuiltinShader::PixelHistoryFS);
VkShaderModuleCreateInfo modinfo = {
VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, NULL, 0, spv.byteSize(), spv.data(),
};
VkResult vkr =
m_pDriver->vkCreateShaderModule(m_pDriver->GetDev(), &modinfo, NULL, &m_PrimitiveIDFS);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
return m_PrimitiveIDFS;
}
// Returns a shader that is equivalent to the given shader, but attempts to remove
// side effects of shader execution for the given entry point (for ex., writes
// to storage buffers/images).
@@ -298,6 +328,7 @@ private:
WrappedVulkan *m_pDriver;
VkShaderModule m_FixedColFS;
VkShaderModule m_PrimitiveIDFS;
// ShaderKey consists of original shader module ID and entry point name.
typedef rdcpair<ResourceId, rdcstr> ShaderKey;
@@ -1410,6 +1441,671 @@ private:
rdcarray<uint64_t> m_OcclusionResults;
};
// Callback used to get values for each fragment.
struct VulkanPixelHistoryPerFragmentCallback : VulkanPixelHistoryCallback
{
VulkanPixelHistoryPerFragmentCallback(WrappedVulkan *vk, PixelHistoryShaderCache *shaderCache,
uint32_t x, uint32_t y, uint32_t sampleMask,
std::map<uint32_t, uint32_t> eventFragments,
VkImage originalImage, VkFormat srcFormat, VkBuffer dstBuffer,
VkImage newImage, VkImageView newImageView)
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, VK_NULL_HANDLE),
m_EventFragments(eventFragments),
m_Image(originalImage),
m_SrcFormat(srcFormat),
m_DstBuffer(dstBuffer),
m_PerFragmentImage(newImage),
m_PerFragmentImageView(newImageView)
{
}
~VulkanPixelHistoryPerFragmentCallback()
{
for(const VkRenderPass &rp : m_RpsToDestroy)
m_pDriver->vkDestroyRenderPass(m_pDriver->GetDev(), rp, NULL);
for(const VkFramebuffer &fb : m_FbsToDestroy)
m_pDriver->vkDestroyFramebuffer(m_pDriver->GetDev(), fb, NULL);
for(const VkPipeline &pipe : m_PipesToDestroy)
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), pipe, NULL);
}
struct Pipelines
{
// Disable all tests, use the new render pass to render into a separate
// attachment, and use fragment shader that outputs primitive ID.
VkPipeline primitiveIdPipe;
// Turn off blending.
VkPipeline shaderOutPipe;
// Enable blending to get post event values.
VkPipeline postModPipe;
};
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
{
if(m_EventFragments.find(eid) == m_EventFragments.end())
return;
VulkanRenderState prevState = m_pDriver->GetCmdRenderState();
VulkanRenderState &state = m_pDriver->GetCmdRenderState();
ResourceId curPipeline = state.graphics.pipeline;
state.EndRenderPass(cmd);
uint32_t numFragmentsInEvent = m_EventFragments[eid];
VkRenderPass newRp = CreateRenderPass(state.renderPass, state.GetFramebuffer(), state.subpass);
VkFramebuffer newFb = CreateFramebuffer(state.GetFramebuffer(), newRp);
Pipelines pipes = CreatePipelines(curPipeline, newRp, eid, false, 0);
state.renderPass = GetResID(newRp);
state.SetFramebuffer(m_pDriver, GetResID(newFb));
VkPipeline pipesIter[3];
pipesIter[0] = pipes.primitiveIdPipe;
pipesIter[1] = pipes.shaderOutPipe;
pipesIter[2] = pipes.postModPipe;
CopyPixelParams colourCopyParams = {};
colourCopyParams.multisampled = false; // TODO: multisampled
colourCopyParams.srcImage = m_PerFragmentImage;
colourCopyParams.srcImageLayout =
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // TODO: image layout
colourCopyParams.srcImageFormat = VK_FORMAT_R32G32B32A32_SFLOAT;
colourCopyParams.imageOffset.x = int32_t(m_X);
colourCopyParams.imageOffset.y = int32_t(m_Y);
colourCopyParams.imageOffset.z = 0;
colourCopyParams.dstBuffer = m_DstBuffer;
for(uint32_t f = 0; f < numFragmentsInEvent; f++)
{
for(uint32_t i = 0; i < 2; i++)
{
m_pDriver->GetCmdRenderState().graphics.pipeline = GetResID(pipesIter[i]);
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(
m_pDriver, cmd, VulkanRenderState::BindGraphics);
{
rdcarray<VkClearAttachment> atts(2);
atts[0].aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
atts[1].aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
VkClearRect rect = {};
rect.rect.offset.x = m_X;
rect.rect.offset.y = m_Y;
rect.rect.extent.width = 1;
rect.rect.extent.height = 1;
rect.baseArrayLayer = 0;
rect.layerCount = 1;
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), (uint32_t)atts.size(), atts.data(), 1,
&rect);
}
// Update stencil reference to the current fragment index, so that we get values
// for a single fragment only.
ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f);
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
if(drawcall->flags & DrawFlags::Indexed)
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
drawcall->indexOffset, drawcall->baseVertex,
drawcall->instanceOffset);
else
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
drawcall->vertexOffset, drawcall->instanceOffset);
state.EndRenderPass(cmd);
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
cmd, colourCopyParams, (fragsProcessed + f) * sizeof(PerFragmentInfo) + 16 * i);
}
}
// Restore the original framebuffer and render pass, so that we can get
// post modification values.
state.SetFramebuffer(prevState.GetFramebuffer(), prevState.GetFramebufferAttachments());
state.renderPass = prevState.renderPass;
colourCopyParams.srcImage = m_Image;
colourCopyParams.srcImageFormat = m_SrcFormat;
// For every fragment except the last one.
// The framebuffer should have the values that it had before this event.
// Should we use the original framebuffer? Or the new framebuffer?
// If we use the new one, it won't have the
for(uint32_t f = 0; f < numFragmentsInEvent - 1; f++)
{
// Get post-modification value, use the original framebuffer attachment.
state.graphics.pipeline = GetResID(pipes.postModPipe);
state.BeginRenderPassAndApplyState(m_pDriver, cmd, VulkanRenderState::BindGraphics);
// Have to reset stencil.
VkClearAttachment att = {};
att.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
VkClearRect rect = {};
rect.rect.offset.x = m_X;
rect.rect.offset.y = m_Y;
rect.rect.extent.width = 1;
rect.rect.extent.height = 1;
rect.baseArrayLayer = 0;
rect.layerCount = 1;
ObjDisp(cmd)->CmdClearAttachments(Unwrap(cmd), 1, &att, 1, &rect);
ObjDisp(cmd)->CmdSetStencilReference(Unwrap(cmd), VK_STENCIL_FACE_FRONT_AND_BACK, f);
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
if(drawcall->flags & DrawFlags::Indexed)
ObjDisp(cmd)->CmdDrawIndexed(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
drawcall->indexOffset, drawcall->baseVertex,
drawcall->instanceOffset);
else
ObjDisp(cmd)->CmdDraw(Unwrap(cmd), drawcall->numIndices, drawcall->numInstances,
drawcall->vertexOffset, drawcall->instanceOffset);
state.EndRenderPass(cmd);
m_pDriver->GetDebugManager()->PixelHistoryCopyPixel(
cmd, colourCopyParams, (fragsProcessed + f) * sizeof(PerFragmentInfo) +
offsetof(struct PerFragmentInfo, postMod));
}
m_EventIndices[eid] = fragsProcessed;
fragsProcessed += numFragmentsInEvent;
m_pDriver->GetCmdRenderState() = prevState;
m_pDriver->GetCmdRenderState().BeginRenderPassAndApplyState(m_pDriver, cmd,
VulkanRenderState::BindGraphics);
}
bool PostDraw(uint32_t eid, VkCommandBuffer cmd) { return false; }
void PostRedraw(uint32_t eid, VkCommandBuffer cmd) {}
// CreateRenderPass creates a new VkRenderpass that is based on the original,
// but substitutes the colour attachment.
VkRenderPass CreateRenderPass(ResourceId originalRp, ResourceId fb, uint32_t subpassIdx)
{
const VulkanCreationInfo::RenderPass &rpInfo =
m_pDriver->GetDebugManager()->GetRenderPassInfo(originalRp);
const VulkanCreationInfo::RenderPass::Subpass &sub = rpInfo.subpasses[subpassIdx];
// Copy color and input attachments, and ignore resolve attachments.
// Since we are only using this renderpass to replay a single draw, we don't
// need to do resolve operations.
rdcarray<VkAttachmentReference> colorAttachments(sub.colorAttachments.size());
rdcarray<VkAttachmentReference> inputAttachments(sub.inputAttachments.size());
for(size_t i = 0; i < sub.colorAttachments.size(); i++)
{
colorAttachments[i].attachment = sub.colorAttachments[i];
colorAttachments[i].layout = sub.colorLayouts[i];
}
for(size_t i = 0; i < sub.inputAttachments.size(); i++)
{
inputAttachments[i].attachment = sub.inputAttachments[i];
inputAttachments[i].layout = sub.inputLayouts[i];
}
VkSubpassDescription subpassDesc = {};
subpassDesc.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpassDesc.inputAttachmentCount = (uint32_t)sub.inputAttachments.size();
subpassDesc.pInputAttachments = inputAttachments.data();
subpassDesc.colorAttachmentCount = (uint32_t)sub.colorAttachments.size();
subpassDesc.pColorAttachments = colorAttachments.data();
rdcarray<VkAttachmentDescription> descs(rpInfo.attachments.size());
for(uint32_t i = 0; i < rpInfo.attachments.size(); i++)
{
descs[i] = {};
descs[i].flags = rpInfo.attachments[i].flags;
descs[i].format = rpInfo.attachments[i].format;
descs[i].samples = rpInfo.attachments[i].samples;
descs[i].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
descs[i].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
descs[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
descs[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
descs[i].initialLayout = rpInfo.attachments[i].initialLayout;
descs[i].finalLayout = rpInfo.attachments[i].finalLayout;
}
const VulkanCreationInfo::Framebuffer &fbInfo =
m_pDriver->GetDebugManager()->GetFramebufferInfo(fb);
for(uint32_t i = 0; i < fbInfo.attachments.size(); i++)
{
if(m_pDriver->GetDebugManager()->GetImageViewInfo(fbInfo.attachments[i].createdView).image ==
GetResID(m_Image))
descs[i].format = VK_FORMAT_R32G32B32A32_SFLOAT;
}
for(uint32_t a = 0; a < subpassDesc.colorAttachmentCount; a++)
{
if(subpassDesc.pColorAttachments[a].attachment != VK_ATTACHMENT_UNUSED)
{
descs[subpassDesc.pColorAttachments[a].attachment].initialLayout =
descs[subpassDesc.pColorAttachments[a].attachment].finalLayout =
subpassDesc.pColorAttachments[a].layout;
}
}
for(uint32_t a = 0; a < subpassDesc.inputAttachmentCount; a++)
{
if(subpassDesc.pInputAttachments[a].attachment != VK_ATTACHMENT_UNUSED)
{
descs[subpassDesc.pInputAttachments[a].attachment].initialLayout =
descs[subpassDesc.pInputAttachments[a].attachment].finalLayout =
subpassDesc.pInputAttachments[a].layout;
}
}
if(sub.depthstencilAttachment != -1)
{
VkAttachmentReference dsAtt = {};
dsAtt.attachment = sub.depthstencilAttachment;
dsAtt.layout = sub.depthLayout;
subpassDesc.pDepthStencilAttachment = &dsAtt;
}
VkRenderPassCreateInfo rpCreateInfo = {};
rpCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
rpCreateInfo.attachmentCount = (uint32_t)descs.size();
rpCreateInfo.subpassCount = 1;
rpCreateInfo.pSubpasses = &subpassDesc;
rpCreateInfo.pAttachments = descs.data();
rpCreateInfo.dependencyCount = 0;
rpCreateInfo.pDependencies = NULL;
VkRenderPass renderPass;
VkResult vkr =
m_pDriver->vkCreateRenderPass(m_pDriver->GetDev(), &rpCreateInfo, NULL, &renderPass);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_RpsToDestroy.push_back(renderPass);
return renderPass;
}
// CreateFramebuffer creates a new VkFramebuffer that is based on the original, but
// substitutes the colour attachment.
VkFramebuffer CreateFramebuffer(ResourceId fb, VkRenderPass newRp)
{
const VulkanCreationInfo::Framebuffer &fbInfo =
m_pDriver->GetDebugManager()->GetFramebufferInfo(fb);
rdcarray<VkImageView> fbAtts(fbInfo.attachments.size());
for(uint32_t i = 0; i < fbInfo.attachments.size(); i++)
{
fbAtts[i] = m_pDriver->GetResourceManager()->GetCurrentHandle<VkImageView>(
fbInfo.attachments[i].createdView);
if(m_pDriver->GetDebugManager()->GetImageViewInfo(fbInfo.attachments[i].createdView).image ==
GetResID(m_Image))
fbAtts[i] = m_PerFragmentImageView;
}
VkFramebufferCreateInfo fbCI = {};
fbCI.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbCI.attachmentCount = (uint32_t)fbAtts.size();
fbCI.pAttachments = fbAtts.data();
fbCI.height = fbInfo.height;
fbCI.width = fbInfo.width;
fbCI.layers = fbInfo.layers;
fbCI.renderPass = newRp;
VkFramebuffer newFB;
VkResult vkr = m_pDriver->vkCreateFramebuffer(m_pDriver->GetDev(), &fbCI, nullptr, &newFB);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_FbsToDestroy.push_back(newFB);
return newFB;
}
// CreatePipelines for getting per fragment information.
Pipelines CreatePipelines(ResourceId pipe, VkRenderPass rp, uint32_t eid, bool dynamicScissor,
uint32_t fragmentIndex)
{
const VulkanCreationInfo::Pipeline &p = m_pDriver->GetDebugManager()->GetPipelineInfo(pipe);
EventFlags eventFlags = m_pDriver->GetEventFlags(eid);
VkShaderModule replacementShaders[5] = {};
// Clean shaders
uint32_t numberOfStages = 5;
for(size_t i = 0; i < numberOfStages; i++)
{
if((eventFlags & PipeStageRWEventFlags(StageFromIndex(i))) != EventFlags::NoFlags)
replacementShaders[i] =
m_ShaderCache->GetShaderWithoutSideEffects(p.shaders[i].module, p.shaders[i].entryPoint);
}
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, pipe);
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
VkPipelineDepthStencilStateCreateInfo *ds =
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
VkPipelineMultisampleStateCreateInfo *ms =
(VkPipelineMultisampleStateCreateInfo *)pipeCreateInfo.pMultisampleState;
VkPipelineViewportStateCreateInfo *vs =
(VkPipelineViewportStateCreateInfo *)pipeCreateInfo.pViewportState;
VkPipelineDynamicStateCreateInfo *dynState =
(VkPipelineDynamicStateCreateInfo *)pipeCreateInfo.pDynamicState;
rdcarray<VkDynamicState> dynamicStates;
RDCASSERT(dynState != NULL);
if(!p.dynamicStates[VkDynamicStencilReference])
{
dynamicStates.resize(dynState->dynamicStateCount);
memcpy(dynamicStates.data(), dynState->pDynamicStates, dynamicStates.byteSize());
dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE);
dynState->dynamicStateCount = (uint32_t)dynamicStates.size();
dynState->pDynamicStates = dynamicStates.data();
}
VkPipelineColorBlendStateCreateInfo *cbs =
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
VkRect2D newScissors[16];
memset(newScissors, 0, sizeof(newScissors));
{
// Disable all tests, but stencil.
rs->cullMode = VK_CULL_MODE_NONE;
rs->rasterizerDiscardEnable = VK_FALSE;
ds->depthTestEnable = VK_FALSE;
ds->depthWriteEnable = VK_FALSE;
ds->stencilTestEnable = VK_TRUE;
ds->depthBoundsTestEnable = VK_FALSE;
ds->front.compareOp = VK_COMPARE_OP_EQUAL;
ds->front.failOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
ds->front.passOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
ds->front.depthFailOp = VK_STENCIL_OP_INCREMENT_AND_CLAMP;
ds->front.compareMask = 0xff;
ds->front.writeMask = 0xff;
// Reference is updated dynamically via vkCmdSetStencilReference.
ds->front.reference = 0;
ds->back = ds->front;
ms->pSampleMask = &m_SampleMask;
// TODO: consider setting colorWriteMask to 0 if we can do that per attachment
// and figure out which attachment it should be on for.
if(m_pDriver->GetDeviceFeatures().depthClamp)
rs->depthClampEnable = true;
// Change scissors unless they are set dynamically.
if(!p.dynamicStates[VkDynamicScissor])
{
for(uint32_t i = 0; i < vs->viewportCount; i++)
{
ScissorToPixel(vs->pViewports[i], newScissors[i]);
}
vs->pScissors = newScissors;
}
else
{
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
if(dynamicScissor)
for(uint32_t i = 0; i < pipestate.views.size(); i++)
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
}
}
// TODO: this is wrong, should take into account subpass.
pipeCreateInfo.subpass = 0;
rdcarray<VkPipelineShaderStageCreateInfo> stages;
stages.resize(pipeCreateInfo.stageCount);
memcpy(stages.data(), pipeCreateInfo.pStages, stages.byteSize());
VkPipelineShaderStageCreateInfo *fsStage = NULL;
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkShaderModule replacement = replacementShaders[StageIndex(stages[i].stage)];
if(replacement != VK_NULL_HANDLE)
stages[i].module = replacement;
if(stages[i].stage == VK_SHADER_STAGE_FRAGMENT_BIT)
fsStage = &stages[i];
}
pipeCreateInfo.pStages = stages.data();
Pipelines pipes = {};
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
&pipeCreateInfo, NULL, &pipes.postModPipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_PipesToDestroy.push_back(pipes.postModPipe);
pipeCreateInfo.renderPass = rp;
// Turn off blending so that we can get shader output value.s
VkPipelineColorBlendAttachmentState *atts =
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
atts[i].blendEnable = VK_FALSE;
vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
&pipeCreateInfo, NULL, &pipes.shaderOutPipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_PipesToDestroy.push_back(pipes.shaderOutPipe);
// Output the primitive ID.
RDCASSERT(fsStage != NULL);
fsStage->module = m_ShaderCache->GetPrimitiveIdShader();
fsStage->pName = "main";
vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
&pipeCreateInfo, NULL, &pipes.primitiveIdPipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_PipesToDestroy.push_back(pipes.primitiveIdPipe);
return pipes;
}
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return false; }
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
void AliasEvent(uint32_t primary, uint32_t alias) {}
uint32_t GetEventOffset(uint32_t eid)
{
auto it = m_EventIndices.find(eid);
RDCASSERT(it != m_EventIndices.end());
return it->second;
}
private:
// For each event, specifies where the occlusion query results start.
std::map<uint32_t, uint32_t> m_EventIndices;
// Number of fragments for each event.
std::map<uint32_t, uint32_t> m_EventFragments;
// Original image for which pixel history was requested.
VkImage m_Image;
VkFormat m_SrcFormat;
VkBuffer m_DstBuffer;
// Number of fragments processed so far.
uint32_t fragsProcessed = 0;
// These were created with VK_FORMAT_R32G32B32A32_SFLOAT format.
// The image is cleared between each replay for a fragment, and is
// used to get shader output value and primitive ID.
VkImageView m_PerFragmentImageView;
VkImage m_PerFragmentImage;
rdcarray<VkPipeline> m_PipesToDestroy;
rdcarray<VkRenderPass> m_RpsToDestroy;
rdcarray<VkFramebuffer> m_FbsToDestroy;
};
// Callback used to determine the shader discard status for each fragment, where
// an event has multiple fragments with some being discarded in a fragment shader.
struct VulkanPixelHistoryDiscardedFragmentsCallback : VulkanPixelHistoryCallback
{
// Key is event ID and value is a list of primitive IDs
std::map<uint32_t, rdcarray<int32_t> > m_Events;
VulkanPixelHistoryDiscardedFragmentsCallback(WrappedVulkan *vk,
PixelHistoryShaderCache *shaderCache, uint32_t x,
uint32_t y, uint32_t sampleMask,
std::map<uint32_t, rdcarray<int32_t> > events,
VkQueryPool occlusionPool)
: VulkanPixelHistoryCallback(vk, shaderCache, x, y, sampleMask, occlusionPool),
m_Events(events)
{
}
~VulkanPixelHistoryDiscardedFragmentsCallback()
{
for(const VkPipeline &pipe : m_PipesToDestroy)
m_pDriver->vkDestroyPipeline(m_pDriver->GetDev(), pipe, NULL);
}
void PreDraw(uint32_t eid, VkCommandBuffer cmd)
{
if(m_Events.find(eid) == m_Events.end())
return;
const rdcarray<int32_t> primIds = m_Events[eid];
VulkanRenderState prevState = m_pDriver->GetCmdRenderState();
VulkanRenderState &state = m_pDriver->GetCmdRenderState();
const VulkanCreationInfo::Pipeline &p =
m_pDriver->GetDebugManager()->GetPipelineInfo(state.graphics.pipeline);
// Create a pipeline with a scissor and colorWriteMask = 0, and disable all tests.
VkPipeline newPipe =
CreatePipeline(state.graphics.pipeline, eid, p.dynamicStates[VkDynamicScissor]);
state.graphics.pipeline = GetResID(newPipe);
state.BindPipeline(m_pDriver, cmd, VulkanRenderState::PipelineBinding::BindGraphics, false);
for(uint32_t i = 0; i < primIds.size(); i++)
{
uint32_t queryId = (uint32_t)m_OcclusionIndices.size();
ObjDisp(cmd)->CmdBeginQuery(Unwrap(cmd), m_OcclusionPool, queryId, 0);
const DrawcallDescription *drawcall = m_pDriver->GetDrawcall(eid);
uint32_t primId = primIds[i];
// TODO once pixel history distinguishes between instances, draw only the instance for
// this fragment
if(drawcall->flags & DrawFlags::Indexed)
ObjDisp(cmd)->CmdDrawIndexed(
Unwrap(cmd), RENDERDOC_NumVerticesPerPrimitive(drawcall->topology),
RDCMAX(1U, drawcall->numInstances),
drawcall->indexOffset + RENDERDOC_VertexOffset(drawcall->topology, primId),
drawcall->baseVertex, drawcall->instanceOffset);
else
ObjDisp(cmd)->CmdDraw(
Unwrap(cmd), RENDERDOC_NumVerticesPerPrimitive(drawcall->topology),
RDCMAX(1U, drawcall->numInstances),
drawcall->vertexOffset + RENDERDOC_VertexOffset(drawcall->topology, primId),
drawcall->instanceOffset);
ObjDisp(cmd)->CmdEndQuery(Unwrap(cmd), m_OcclusionPool, queryId);
m_OcclusionIndices[make_rdcpair<uint32_t, uint32_t>(eid, primId)] = queryId;
}
m_pDriver->GetCmdRenderState() = prevState;
m_pDriver->GetCmdRenderState().BindPipeline(
m_pDriver, cmd, VulkanRenderState::PipelineBinding::BindGraphics, false);
}
VkPipeline CreatePipeline(ResourceId pipe, uint32_t eid, bool dynamicScissor)
{
const VulkanCreationInfo::Pipeline &p = m_pDriver->GetDebugManager()->GetPipelineInfo(pipe);
EventFlags eventFlags = m_pDriver->GetEventFlags(eid);
VkShaderModule replacementShaders[5] = {};
// Clean shaders
uint32_t numberOfStages = 5;
for(size_t i = 0; i < numberOfStages; i++)
{
if((eventFlags & PipeStageRWEventFlags(StageFromIndex(i))) != EventFlags::NoFlags)
replacementShaders[i] =
m_ShaderCache->GetShaderWithoutSideEffects(p.shaders[i].module, p.shaders[i].entryPoint);
}
VkGraphicsPipelineCreateInfo pipeCreateInfo = {};
m_pDriver->GetShaderCache()->MakeGraphicsPipelineInfo(pipeCreateInfo, pipe);
VkPipelineRasterizationStateCreateInfo *rs =
(VkPipelineRasterizationStateCreateInfo *)pipeCreateInfo.pRasterizationState;
VkPipelineDepthStencilStateCreateInfo *ds =
(VkPipelineDepthStencilStateCreateInfo *)pipeCreateInfo.pDepthStencilState;
VkPipelineMultisampleStateCreateInfo *ms =
(VkPipelineMultisampleStateCreateInfo *)pipeCreateInfo.pMultisampleState;
VkPipelineViewportStateCreateInfo *vs =
(VkPipelineViewportStateCreateInfo *)pipeCreateInfo.pViewportState;
VkPipelineColorBlendStateCreateInfo *cbs =
(VkPipelineColorBlendStateCreateInfo *)pipeCreateInfo.pColorBlendState;
VkRect2D newScissors[16];
memset(newScissors, 0, sizeof(newScissors));
{
// Disable all tests, but stencil.
rs->cullMode = VK_CULL_MODE_NONE;
rs->rasterizerDiscardEnable = VK_FALSE;
ds->depthTestEnable = VK_FALSE;
ds->depthWriteEnable = VK_FALSE;
ds->stencilTestEnable = VK_FALSE;
ds->depthBoundsTestEnable = VK_FALSE;
ms->pSampleMask = &m_SampleMask;
VkPipelineColorBlendAttachmentState *atts =
(VkPipelineColorBlendAttachmentState *)cbs->pAttachments;
for(uint32_t i = 0; i < cbs->attachmentCount; i++)
atts[i].colorWriteMask = 0;
if(m_pDriver->GetDeviceFeatures().depthClamp)
rs->depthClampEnable = true;
// Change scissors unless they are set dynamically.
if(!p.dynamicStates[VkDynamicScissor])
{
for(uint32_t i = 0; i < vs->viewportCount; i++)
{
ScissorToPixel(vs->pViewports[i], newScissors[i]);
}
vs->pScissors = newScissors;
}
else
{
VulkanRenderState &pipestate = m_pDriver->GetCmdRenderState();
if(dynamicScissor)
for(uint32_t i = 0; i < pipestate.views.size(); i++)
ScissorToPixel(pipestate.views[i], pipestate.scissors[i]);
}
}
pipeCreateInfo.subpass = 0;
rdcarray<VkPipelineShaderStageCreateInfo> stages;
stages.resize(pipeCreateInfo.stageCount);
memcpy(stages.data(), pipeCreateInfo.pStages, stages.byteSize());
for(uint32_t i = 0; i < pipeCreateInfo.stageCount; i++)
{
VkShaderModule replacement = replacementShaders[StageIndex(stages[i].stage)];
if(replacement != VK_NULL_HANDLE)
stages[i].module = replacement;
}
pipeCreateInfo.pStages = stages.data();
VkPipeline newPipe;
VkResult vkr = m_pDriver->vkCreateGraphicsPipelines(m_pDriver->GetDev(), VK_NULL_HANDLE, 1,
&pipeCreateInfo, NULL, &newPipe);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
m_PipesToDestroy.push_back(newPipe);
return newPipe;
}
void FetchOcclusionResults()
{
m_OcclusionResults.resize(m_OcclusionIndices.size());
VkResult vkr = ObjDisp(m_pDriver->GetDev())
->GetQueryPoolResults(Unwrap(m_pDriver->GetDev()), m_OcclusionPool, 0,
(uint32_t)m_OcclusionIndices.size(),
m_OcclusionResults.byteSize(),
m_OcclusionResults.data(), sizeof(uint64_t),
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
}
bool PrimitiveDiscarded(uint32_t eid, uint32_t primId)
{
auto it = m_OcclusionIndices.find(make_rdcpair<uint32_t, uint32_t>(eid, primId));
if(it == m_OcclusionIndices.end())
return false;
return m_OcclusionResults[it->second] == 0;
}
bool PostDraw(uint32_t eid, VkCommandBuffer cmd) { return false; }
void PostRedraw(uint32_t eid, VkCommandBuffer cmd) {}
void PreDispatch(uint32_t eid, VkCommandBuffer cmd) {}
bool PostDispatch(uint32_t eid, VkCommandBuffer cmd) { return false; }
void PostRedispatch(uint32_t eid, VkCommandBuffer cmd) {}
void PreMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
bool PostMisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) { return false; }
void PostRemisc(uint32_t eid, DrawFlags flags, VkCommandBuffer cmd) {}
void PreEndCommandBuffer(VkCommandBuffer cmd) {}
void AliasEvent(uint32_t primary, uint32_t alias) {}
private:
std::map<rdcpair<uint32_t, uint32_t>, uint32_t> m_OcclusionIndices;
rdcarray<uint64_t> m_OcclusionResults;
rdcarray<VkPipeline> m_PipesToDestroy;
};
bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resources,
VkExtent3D extent, VkFormat format,
uint32_t numEvents)
@@ -1436,7 +2132,7 @@ bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resou
imgInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
// Device local resources:
imgInfo.format = format;
imgInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
imgInfo.extent.width = extent.width;
imgInfo.extent.height = extent.height;
imgInfo.extent.depth = 1;
@@ -1482,7 +2178,7 @@ bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resou
VkImageViewCreateInfo viewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
viewInfo.image = colorImage;
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
viewInfo.format = format;
viewInfo.format = VK_FORMAT_R32G32B32A32_SFLOAT;
viewInfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
vkr = m_pDriver->vkCreateImageView(m_Device, &viewInfo, NULL, &colorImageView);
@@ -1496,7 +2192,7 @@ bool VulkanDebugManager::PixelHistorySetupResources(PixelHistoryResources &resou
RDCASSERTEQUAL(vkr, VK_SUCCESS);
VkBufferCreateInfo bufferInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
bufferInfo.size = AlignUp((uint32_t)(numEvents * sizeof(EventInfo)), 512U);
bufferInfo.size = AlignUp((uint32_t)(numEvents * sizeof(EventInfo)), 4096U);
bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT;
vkr = m_pDriver->vkCreateBuffer(m_Device, &bufferInfo, NULL, &dstBuffer);
@@ -1746,15 +2442,17 @@ void UpdateTestsFailed(const TestsFailedCallback *tfCb, uint32_t eventId, uint32
}
}
void FillInColor(ResourceFormat fmt, const PixelHistoryValue &value, ModificationValue &mod)
{
FloatVector v4 = ConvertComponents(fmt, value.color);
memcpy(mod.col.floatValue, &v4.x, sizeof(v4));
}
void FillInColors(const EventInfo &ei, VkFormat format, PixelModification &mod)
{
ResourceFormat fmt = MakeResourceFormat(format);
FloatVector premod = ConvertComponents(fmt, ei.premod.color);
FloatVector postmod = ConvertComponents(fmt, ei.postmod.color);
memcpy(mod.preMod.col.floatValue, &premod.x, sizeof(premod));
memcpy(mod.postMod.col.floatValue, &postmod.x, sizeof(postmod));
FillInColor(fmt, ei.premod, mod.preMod);
FillInColor(fmt, ei.postmod, mod.postMod);
}
rdcarray<PixelModification> VulkanReplay::PixelHistory(rdcarray<EventUsage> events,
@@ -1858,6 +2556,7 @@ rdcarray<PixelModification> VulkanReplay::PixelHistory(rdcarray<EventUsage> even
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
tfCb->FetchOcclusionResults();
ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), tfOcclusionPool, NULL);
}
for(size_t ev = 0; ev < events.size(); ev++)
@@ -1895,14 +2594,13 @@ rdcarray<PixelModification> VulkanReplay::PixelHistory(rdcarray<EventUsage> even
// Try to read memory back
void *bufPtr = NULL;
EventInfo *eventsInfo;
VkResult vkr =
m_pDriver->vkMapMemory(dev, resources.bufferMemory, 0, VK_WHOLE_SIZE, 0, (void **)&bufPtr);
m_pDriver->vkMapMemory(dev, resources.bufferMemory, 0, VK_WHOLE_SIZE, 0, (void **)&eventsInfo);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
EventInfo *eventsInfo = (EventInfo *)bufPtr;
for(size_t h = 0; h < history.size(); h++)
std::map<uint32_t, uint32_t> eventsWithFrags;
for(size_t h = 0; h < history.size();)
{
PixelModification &mod = history[h];
@@ -1913,11 +2611,119 @@ rdcarray<PixelModification> VulkanReplay::PixelHistory(rdcarray<EventUsage> even
mod.postMod.depth = ei.postmod.depth.fdepth;
mod.postMod.stencil = ei.postmod.stencil;
int32_t frags = int32_t(ei.dsWithoutShaderDiscard[0]);
int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[0]);
mod.shaderOut.col.intValue[0] = frags;
mod.shaderOut.col.intValue[1] = fragsClipped;
bool someFragsClipped = (fragsClipped < frags);
mod.primitiveID = someFragsClipped;
if(frags > 0)
eventsWithFrags[mod.eventId] = frags;
for(int32_t f = 1; f < frags; f++)
{
history.insert(h + 1, mod);
}
for(int32_t f = 0; f < frags; f++)
history[h + f].fragIndex = f;
h += RDCMAX(1, frags);
RDCDEBUG(
"PixelHistory event id: %u, fixed shader stencilValue = %u, original shader stencilValue = "
"%u",
mod.eventId, ei.dsWithoutShaderDiscard[0], ei.dsWithShaderDiscard[0]);
}
m_pDriver->vkUnmapMemory(dev, resources.bufferMemory);
if(eventsWithFrags.size() > 0)
{
// Replay to get shader output value, post modification value and primitive ID for every
// fragment.
VulkanPixelHistoryPerFragmentCallback perFragmentCB(
m_pDriver, shaderCache, x, y, sampleMask, eventsWithFrags,
GetResourceManager()->GetCurrentHandle<VkImage>(target), imginfo.format,
resources.dstBuffer, resources.colorImage, resources.colorImageView);
m_pDriver->ReplayLog(0, events.back().eventId, eReplay_Full);
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
PerFragmentInfo *bp = NULL;
vkr = m_pDriver->vkMapMemory(dev, resources.bufferMemory, 0, VK_WHOLE_SIZE, 0, (void **)&bp);
RDCASSERTEQUAL(vkr, VK_SUCCESS);
// Retrieve primitive ID values where fragment shader discarded some
// fragments. For these primitives we are going to perform an occlusion
// query to see if a primitive was discarded.
std::map<uint32_t, rdcarray<int32_t> > discardedPrimsEvents;
uint32_t primitivesToCheck = 0;
for(size_t h = 0; h < history.size(); h++)
{
uint32_t eid = history[h].eventId;
if(eventsWithFrags.find(eid) == eventsWithFrags.end())
continue;
uint32_t f = history[h].fragIndex;
bool someFragsClipped = (history[h].primitiveID == 1);
int32_t primId = bp[perFragmentCB.GetEventOffset(eid) + f].primitiveID;
history[h].primitiveID = primId;
if(someFragsClipped)
{
discardedPrimsEvents[eid].push_back(primId);
primitivesToCheck++;
}
}
if(primitivesToCheck > 0)
{
VkQueryPool occlPool;
CreateOcclusionPool(m_pDriver, primitivesToCheck, &occlPool);
// Replay to see which primitives were discarded.
VulkanPixelHistoryDiscardedFragmentsCallback discardedCb(
m_pDriver, shaderCache, x, y, sampleMask, discardedPrimsEvents, occlPool);
m_pDriver->ReplayLog(0, events.back().eventId, eReplay_Full);
m_pDriver->SubmitCmds();
m_pDriver->FlushQ();
discardedCb.FetchOcclusionResults();
ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), occlPool, NULL);
for(size_t h = 0; h < history.size(); h++)
history[h].shaderDiscarded =
discardedCb.PrimitiveDiscarded(history[h].eventId, history[h].primitiveID);
}
uint32_t discardOffset = 0;
ResourceFormat resourceFmt = MakeResourceFormat(imginfo.format);
ResourceFormat shaderOutFormat = MakeResourceFormat(VK_FORMAT_R32G32B32A32_SFLOAT);
for(size_t h = 0; h < history.size(); h++)
{
uint32_t eid = history[h].eventId;
uint32_t f = history[h].fragIndex;
// Reset discard offset if this is a new event.
if(h > 0 && (eid != history[h - 1].eventId))
discardOffset = 0;
if(eventsWithFrags.find(eid) != eventsWithFrags.end())
{
if(history[h].shaderDiscarded)
{
discardOffset++;
// Copy previous post-mod value if its not the first event
if(h > 0)
history[h].postMod = history[h - 1].postMod;
continue;
}
uint32_t offset = perFragmentCB.GetEventOffset(eid) + f - discardOffset;
PixelHistoryValue val;
memcpy(&val, bp[offset].shaderOut, 4 * 4);
FillInColor(shaderOutFormat, val, history[h].shaderOut);
if((h < history.size() - 1) && (history[h].eventId == history[h + 1].eventId))
{
// Get post-modification value if this is not the last fragment for the event.
memcpy(&val, bp[offset].postMod, 4 * 4);
FillInColor(resourceFmt, val, history[h].postMod);
}
}
}
}
m_pDriver->vkUnmapMemory(dev, resources.bufferMemory);
GetDebugManager()->PixelHistoryDestroyResources(resources);
@@ -91,6 +91,8 @@ static const BuiltinShaderConfig builtinShaders[] = {
rdcspv::ShaderStage::Fragment, FeatureCheck::NoCheck, true},
{BuiltinShader::TexRemapSInt, EmbeddedResource(glsl_texremap_frag),
rdcspv::ShaderStage::Fragment, FeatureCheck::NoCheck, true},
{BuiltinShader::PixelHistoryFS, EmbeddedResource(glsl_pixelhistory_frag),
rdcspv::ShaderStage::Fragment, FeatureCheck::NoCheck, true},
};
RDCCOMPILE_ASSERT(ARRAY_COUNT(builtinShaders) == arraydim<BuiltinShader>(),
@@ -54,6 +54,7 @@ enum class BuiltinShader
TexRemapFloat,
TexRemapUInt,
TexRemapSInt,
PixelHistoryFS,
Count,
};
+1
View File
@@ -599,6 +599,7 @@
<None Include="data\glsl\minmaxresult.comp" />
<None Include="data\glsl\minmaxtile.comp" />
<None Include="data\glsl\ms2array.comp" />
<None Include="data\glsl\pixelhistory.frag" />
<None Include="data\glsl\quadresolve.frag" />
<None Include="data\glsl\quadwrite.frag" />
<None Include="data\glsl\texdisplay.frag" />
+3
View File
@@ -998,6 +998,9 @@
<None Include="data\glsl\ms2array.comp">
<Filter>Resources\glsl</Filter>
</None>
<None Include="data\glsl\pixelhistory.frag">
<Filter>Resources\glsl</Filter>
</None>
<None Include="data\glsl\quadresolve.frag">
<Filter>Resources\glsl</Filter>
</None>