diff --git a/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp b/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp index 7dca50bba..b3c13ae94 100644 --- a/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp +++ b/renderdoc/driver/d3d12/d3d12_pixelhistory.cpp @@ -3297,8 +3297,10 @@ rdcarray D3D12Replay::PixelHistory(rdcarray event int32_t frags = int32_t(ei.dsWithoutShaderDiscard[0]); int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[0]); - bool someFragsClipped = (fragsClipped < frags); - mod.primitiveID = someFragsClipped; + if(fragsClipped < frags) + mod.primitiveID = fragsClipped; + else + mod.primitiveID = 0; if(frags > 0) { @@ -3344,6 +3346,7 @@ rdcarray D3D12Replay::PixelHistory(rdcarray event // 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> discardedPrimsEvents; + std::map fragsClipped; uint32_t primitivesToCheck = 0; for(size_t h = 0; h < history.size(); h++) { @@ -3353,11 +3356,23 @@ rdcarray D3D12Replay::PixelHistory(rdcarray event if(perFragmentCB.ContainsEvent(eid)) { uint32_t f = history[h].fragIndex; - bool someFragsClipped = (history[h].primitiveID == 1); + fragsClipped[eid] = history[h].primitiveID; + bool someFragsClipped = (history[h].primitiveID >= 1); int32_t primId = fragInfo[perFragmentCB.GetEventOffset(eid) + f].primitiveID; history[h].primitiveID = primId; if(someFragsClipped) { + // in scenarios with multiple fragments with some discarding, both the primitive IDs for + // all fragments (discarding and non-discarding) as well as shader outs are all fetched at + // once. Because we use stencil counting to fetch (potentially discarded) shader outs we + // will just get all the non-discarded shader outs in the first N fragments without + // knowing which is which. + // + // we could leave the primitive IDs and then below in DiscardedFragmentsCallback try to + // reorder once we know which primitives discarded and which didn't, and assign the first + // N successful fragments, but for now we drop the primitive ID information + history[h].primitiveID = ~0U; + discardedPrimsEvents[eid].push_back(primId); primitivesToCheck++; } @@ -3394,6 +3409,25 @@ rdcarray D3D12Replay::PixelHistory(rdcarray event for(size_t h = 0; h < history.size(); h++) { + // if we dropped the primitive IDs we don't know which individual fragments discarded. + // We've already removed any primitive ID information so to stay consistent since we know + // how many total fragments discarded and how many didn't, we keep the shader out & tex + // after for the first N successful fragments, and explicily mark the last M as discarded. + // This loses ordering information but maintains accuracy about relative numbers. + if(history[h].primitiveID == ~0U) + { + const uint32_t eid = history[h].eventId; + if(eventsWithFrags.find(eid) != eventsWithFrags.end() && + history[h].fragIndex >= fragsClipped[eid]) + { + if(history[h].Passed()) + { + history[h].shaderDiscarded = true; + } + } + continue; + } + history[h].shaderDiscarded = discardedCb.PrimitiveDiscarded(history[h].eventId, history[h].primitiveID); } @@ -3423,6 +3457,7 @@ rdcarray D3D12Replay::PixelHistory(rdcarray event // Copy previous post-mod value if its not the first event if(h > 0) { + history[h].preMod = history[h - 1].postMod; history[h].postMod.col = history[h - 1].postMod.col; history[h].postMod.depth = history[h - 1].postMod.depth; if(!hasDepth) diff --git a/renderdoc/driver/gl/gl_pixelhistory.cpp b/renderdoc/driver/gl/gl_pixelhistory.cpp index 398deaee6..91eb8d9df 100644 --- a/renderdoc/driver/gl/gl_pixelhistory.cpp +++ b/renderdoc/driver/gl/gl_pixelhistory.cpp @@ -1035,7 +1035,8 @@ struct ScopedReadPixelsSanitiser void readPixelValues(WrappedOpenGL *driver, const GLPixelHistoryResources &resources, const CopyFramebuffer ©Framebuffer, rdcarray &history, - int historyIndex, ModType modType, bool readStencil, uint32_t numPixels) + int historyIndex, ModType modType, bool readStencil, uint32_t numPixels, + bool perfrag) { ScopedReadPixelsSanitiser scope; @@ -1139,6 +1140,9 @@ void readPixelValues(WrappedOpenGL *driver, const GLPixelHistoryResources &resou } } + if(perfrag && history[historyIndex + i].shaderDiscarded) + continue; + if(modType == ModType::PreMod) history[historyIndex + i].preMod = modValue; else @@ -1269,7 +1273,7 @@ void QueryPrePostModPixelValues(WrappedOpenGL *driver, GLPixelHistoryResources & if(premodCopyFramebuffer.framebufferId != ~0u) { readPixelValues(driver, resources, premodCopyFramebuffer, history, preModLastIdx, - ModType::PreMod, true, (uint32_t)(i - preModLastIdx)); + ModType::PreMod, true, (uint32_t)(i - preModLastIdx), false); } preModLastIdx = int(i); } @@ -1342,7 +1346,7 @@ void QueryPrePostModPixelValues(WrappedOpenGL *driver, GLPixelHistoryResources & if(postmodCopyFramebuffer.framebufferId != ~0u) { readPixelValues(driver, resources, postmodCopyFramebuffer, history, postModLastIdx, - ModType::PostMod, true, (uint32_t)(i - postModLastIdx)); + ModType::PostMod, true, (uint32_t)(i - postModLastIdx), false); } postModLastIdx = int(i); } @@ -1376,12 +1380,12 @@ void QueryPrePostModPixelValues(WrappedOpenGL *driver, GLPixelHistoryResources & if(numSamples == 1 && premodCopyFramebuffer.framebufferId != 0u) { readPixelValues(driver, resources, premodCopyFramebuffer, history, preModLastIdx, - ModType::PreMod, true, int(modEvents.size()) - preModLastIdx); + ModType::PreMod, true, int(modEvents.size()) - preModLastIdx, false); } if(numSamples == 1 && postmodCopyFramebuffer.framebufferId != 0u) { readPixelValues(driver, resources, postmodCopyFramebuffer, history, postModLastIdx, - ModType::PostMod, true, int(modEvents.size()) - postModLastIdx); + ModType::PostMod, true, int(modEvents.size()) - preModLastIdx, false); } } @@ -1972,6 +1976,8 @@ void QueryShaderOutPerFragment(WrappedOpenGL *driver, GLReplay *replay, historyIndex->postMod.stencil = -2; else historyIndex->postMod.stencil = -1; + if(historyIndex > history.begin() && (historyIndex - 1)->eventId == historyIndex->eventId) + historyIndex->preMod = (historyIndex - 1)->postMod; historyIndex++; continue; } @@ -2356,8 +2362,6 @@ void QueryPrePostModPerFragment(WrappedOpenGL *driver, GLReplay *replay, if(historyIndex + j + 1 < history.end() && (curFragHistoryIndex + 1)->eventId == curFragHistoryIndex->eventId) curFragHistoryIndex->postMod.stencil = -2; - else - curFragHistoryIndex->postMod.stencil = -1; if(numSamples > 1) historyIndex++; @@ -2403,7 +2407,7 @@ void QueryPrePostModPerFragment(WrappedOpenGL *driver, GLReplay *replay, { readPixelValues(driver, resources, premodCopyFramebuffer, history, preModLastJ + int(historyIndex - history.begin()), ModType::PreMod, - false, (uint32_t)(j - preModLastJ)); + false, (uint32_t)(j - preModLastJ), true); } preModLastJ = int(j); } @@ -2464,7 +2468,7 @@ void QueryPrePostModPerFragment(WrappedOpenGL *driver, GLReplay *replay, { readPixelValues(driver, resources, postmodCopyFramebuffer, history, postModLastJ + int(historyIndex - history.begin()), ModType::PostMod, - false, (uint32_t)(j - postModLastJ)); + false, (uint32_t)(j - postModLastJ), true); } postModLastJ = int(j); } @@ -2583,14 +2587,14 @@ void QueryPrePostModPerFragment(WrappedOpenGL *driver, GLReplay *replay, { readPixelValues(driver, resources, premodCopyFramebuffer, history, preModLastJ + int(historyIndex - history.begin()), ModType::PreMod, false, - numFragments - preModLastJ); + numFragments - preModLastJ, true); } if(numSamples == 1 && postmodCopyFramebuffer.framebufferId != ~0u) { readPixelValues(driver, resources, postmodCopyFramebuffer, history, postModLastJ + int(historyIndex - history.begin()), ModType::PostMod, false, - numFragments - postModLastJ); + numFragments - postModLastJ, true); } state.ApplyState(driver); @@ -2838,7 +2842,8 @@ void CalculateFragmentDepthTests(WrappedOpenGL *driver, GLPixelHistoryResources depthBits = 16; } - history[historyIndex].CheckDepthTestQuantised(depthBits, MakeCompareFunc(depthFunc)); + if(history[historyIndex].preMod.depth >= 0.0f && history[historyIndex].shaderOut.depth >= 0.0f) + history[historyIndex].CheckDepthTestQuantised(depthBits, MakeCompareFunc(depthFunc)); if(HasExt[EXT_depth_bounds_test] && GL.glIsEnabled(eGL_DEPTH_BOUNDS_TEST_EXT)) { diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index e50a0410d..26dd37606 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -4822,8 +4822,11 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even int32_t frags = int32_t(ei.dsWithoutShaderDiscard[4]); int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[4]); - bool someFragsClipped = (fragsClipped < frags); - mod.primitiveID = someFragsClipped; + if(fragsClipped < frags) + mod.primitiveID = fragsClipped; + else + mod.primitiveID = 0; + // Draws in secondary command buffers will fail this check, // so nothing else needs to be checked in the callback itself. if(frags > 0) @@ -4911,6 +4914,7 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even // fragments. For these primitives we are going to perform an occlusion // query to see if a primitive was discarded. std::map> discardedPrimsEvents; + std::map fragsClipped; uint32_t primitivesToCheck = 0; for(size_t h = 0; h < history.size(); h++) { @@ -4918,11 +4922,23 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even if(eventsWithFrags.find(eid) == eventsWithFrags.end()) continue; uint32_t f = history[h].fragIndex; - bool someFragsClipped = (history[h].primitiveID == 1); + fragsClipped[eid] = history[h].primitiveID; + bool someFragsClipped = (history[h].primitiveID >= 1); int32_t primId = bp[perFragmentCB.GetEventOffset(eid) + f].primitiveID; history[h].primitiveID = primId; if(someFragsClipped) { + // in scenarios with multiple fragments with some discarding, both the primitive IDs for + // all fragments (discarding and non-discarding) as well as shader outs are all fetched at + // once. Because we use stencil counting to fetch (potentially discarded) shader outs we + // will just get all the non-discarded shader outs in the first N fragments without + // knowing which is which. + // + // we could leave the primitive IDs and then below in DiscardedFragmentsCallback try to + // reorder once we know which primitives discarded and which didn't, and assign the first + // N successful fragments, but for now we drop the primitive ID information + history[h].primitiveID = ~0U; + discardedPrimsEvents[eid].push_back(primId); primitivesToCheck++; } @@ -4948,8 +4964,29 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even ObjDisp(dev)->DestroyQueryPool(Unwrap(dev), occlPool, NULL); for(size_t h = 0; h < history.size(); h++) + { + // if we dropped the primitive IDs we don't know which individual fragments discarded. + // We've already removed any primitive ID information so to stay consistent since we know + // how many total fragments discarded and how many didn't, we keep the shader out & tex + // after for the first N successful fragments, and explicily mark the last M as discarded. + // This loses ordering information but maintains accuracy about relative numbers. + if(history[h].primitiveID == ~0U) + { + const uint32_t eid = history[h].eventId; + if(eventsWithFrags.find(eid) != eventsWithFrags.end() && + history[h].fragIndex >= fragsClipped[eid]) + { + if(history[h].Passed()) + { + history[h].shaderDiscarded = true; + } + } + continue; + } + history[h].shaderDiscarded = discardedCb.PrimitiveDiscarded(history[h].eventId, history[h].primitiveID); + } } } else @@ -4982,6 +5019,7 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even // Copy previous post-mod value if its not the first event if(h > 0) { + history[h].preMod = history[h - 1].postMod; history[h].postMod.col = history[h - 1].postMod.col; history[h].postMod.depth = history[h - 1].postMod.depth; if(cb.GetDepthFormat(eid) == VK_FORMAT_UNDEFINED) diff --git a/util/test/demos/test_common.cpp b/util/test/demos/test_common.cpp index 133313e06..73d011200 100644 --- a/util/test/demos/test_common.cpp +++ b/util/test/demos/test_common.cpp @@ -913,15 +913,16 @@ void init() {Vec3f(-0.4f, 0.8f, 0.33f), Vec4f(0.0f, 0.0f, -1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, }); - // scissor does clip some but passes where above fails + // multiple fragments overlapping the same pixel where only one discards PerFragDiscard = makeDraw({ - {Vec3f(-0.7f, -0.2f, 0.33f), Vec4f(-1.0f, -1.0f, -1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, - {Vec3f(-0.8f, 0.0f, 0.33f), Vec4f(-1.0f, -1.0f, -1.0f, 1.0f), Vec2f(1.0f, 0.0f)}, - {Vec3f(-0.6f, 0.0f, 0.33f), Vec4f(-1.0f, -1.0f, -1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, - + // for now we discard the first one, because Vulkan/D3D12 can't determine ordering {Vec3f(-0.7f, -0.2f, 0.33f), Vec4f(1.0f, 1.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, {Vec3f(-0.8f, 0.0f, 0.33f), Vec4f(1.0f, 1.0f, 1.0f, 1.0f), Vec2f(1.0f, 0.0f)}, {Vec3f(-0.6f, 0.0f, 0.33f), Vec4f(1.0f, 1.0f, 1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + + {Vec3f(-0.7f, -0.2f, 0.33f), Vec4f(-1.0f, -1.0f, -1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-0.8f, 0.0f, 0.33f), Vec4f(-1.0f, -1.0f, -1.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + {Vec3f(-0.6f, 0.0f, 0.33f), Vec4f(-1.0f, -1.0f, -1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, }); }; diff --git a/util/test/rdtest/shared/Pixel_History.py b/util/test/rdtest/shared/Pixel_History.py index 0af8bc31e..5087600a5 100644 --- a/util/test/rdtest/shared/Pixel_History.py +++ b/util/test/rdtest/shared/Pixel_History.py @@ -840,22 +840,20 @@ class Pixel_History(rdtest.TestCase): 'event_name': 'Begin RenderPass', 'passed': True }, + { + 'event_name': 'Per-Fragment discarding', + 'passed': True, + 'shader_out_col': fmt_adjusted(1, 1, 1, alpha_value), + 'shader_out_depth': 0.33, + 'post_mod_col': fmt_clamped(1, 1, 1, alpha_value), + 'unknown_post_mod_stencil': True, + }, { 'event_name': 'Per-Fragment discarding', 'passed': False, 'shader_discarded': True, 'shader_out_col': (0, 0, 0, 0), 'shader_out_depth': -1, - 'unknown_post_mod_stencil': True, - 'primitive_id': 0, - }, - { - 'event_name': 'Per-Fragment discarding', - 'passed': True, - 'primitive_id': 1, - 'shader_out_col': fmt_adjusted(1, 1, 1, alpha_value), - 'shader_out_depth': 0.33, - 'post_mod_col': fmt_clamped(1, 1, 1, alpha_value), }, ] # can't distinguish per-fragment results in secondaries @@ -973,10 +971,10 @@ class Pixel_History(rdtest.TestCase): self.error( f"postmod stencil at EID {m.eventId} primitive {m.primitiveID}: {m.postMod.stencil} is not unknown") - if not rdtest.value_compare(m.postMod.depth, n.preMod.depth): + if not rdtest.value_compare(m.postMod.depth, n.preMod.depth, eps = 1.0e-5): self.error( f"postmod depth at EID {m.eventId} primitive {m.primitiveID}: {m.postMod.depth} " + - f"doesn't match premod at next primitive {n.primitiveID}: {m.preMod.depth}") + f"doesn't match premod at next primitive {n.primitiveID}: {n.preMod.depth}") epsilon = self.epsilon @@ -984,6 +982,10 @@ class Pixel_History(rdtest.TestCase): a = (m.postMod.depth, m.postMod.stencil) b = (n.preMod.depth, n.preMod.stencil) + if a[1] == -2 or b[1] == -2: + a = (a[0], -2) + b = (b[0], -2) + epsilon = 1.0e-5 if not rdtest.value_compare(a, b, eps=epsilon):