Early Z Culling, UAVs und Tiefentests
Verfasst: 09.09.2012, 00:43
Warum gibt es eigentlich keinen post-Pixel-Shader Z-Reject für UAVs?
Die deutsche Spieleentwickler-Community (seit 1999).
https://www.zfx.info/
UAVs in Zukunft ein Flag geben (write-through on z-pass; im Pixel-Shader selbst ist die UAV nur read-only), und wenn der Pixel-Shader den z-Test passiert, den write-through durchführen. Natürlich sind das Einschränkungen; aber dennoch würde das die Verwendbarkeit von UAVs wohl doch einigermaßen erhöhen.CodingCat hat geschrieben:Weil dann längst alles zu spät ist. Wer weiß, was in der Zwischenzeit reingeschrieben wurde.
Ja natürlich kosten Features Zusatz-Hardware; wenn ich die flickernde Scheiße hier betrachte wäre mir das das aber wert. Der einzige Ausweg ist wohl, wie es mir aktuell erscheint, ein z-Prepass.CodingCat hat geschrieben:Kostet aber vermutlich Zusatz-Hardware, weil der ganze Kram dann zwischengespeichert werden muss?
Code: Alles auswählen
int lastDepthOrLocked = 0;
do
{
// Optional early-out, if beneficial
if (depth >= abs(DepthBuffer[pos]))
break;
// Update depth buffer
InterlockedMin(DepthBuffer[pos], depth, lastDepthOrLocked);
if (depth < lastDepthOrLocked)
{
// Lock frame buffer via depth buffer
int lastDepth = 0;
InterlockedCompareExchange(DepthBuffer[pos], depth, -depth, lastDepth);
if (lastDepth == depth)
{
// Update UAVs
// Unlock frame buffer via depth buffer
InterlockedExchange(DepthBuffer[pos], depth);
}
}
} while(lastDepthOrLocked < 0);
Naja, es wohl offenbar genau wie ich vermutet hab:CodingCat hat geschrieben:dot: Ja, du kannst es mit [earlydepthstencil] vor dem Pixel Shader erzwingen. Und nein, Early-Z funktioniert auch bei UAV-Access. Wieso auch nicht, UAVs garantieren eh so gut wie nichts. Sogar eigene Tiefen rausschreiben geht mit Early-Z, solange du mit SV_Depth[Less|LessEqual|GreaterEqual|Greater] garantierst, dass deine Tiefen die für korrektes Early-Z notwendige Monotonie einhalten. (Über die von dir angesprochene Hi-Z-Optimierung sagt [earlydepthstencil] btw. nichts aus.)
Ist imo auch logisch, da eine Optimierung nicht einfach so das beobachtbare Verhalten des Programms verändern sollte. Allerdings kannst du es wohl mit [earlydepthstencil] erzwingen, das is inzwischen wohl irgendwie wieder aus meinem Bewusstsein verschwunden.MSDN hat geschrieben:An application can force early depth-stencil testing by supplying the attribute or the hardware may execute early depth-stencil testing provided no depth values are written and no unordered access operations are performed in a shader as an optimization.
Vorsicht mit Spin Locks: Immer im Locked-Fall Instruktionen überspringen und im Unlocked-Fall "spinnen" (umgekehrt zu typischen Spin Locks auf CPU, siehe Beispiel), andernfalls haut dich das SIMT-Modell direkt in den Deadlock. (Im Locked-Fall spinnende Threads würden sämtliche Unlocked-Threads in derselben Thread-Gruppe blockieren.)eXile hat geschrieben:Ich hatte gestern Abend fast das gleiche implementiert; auch wenn ich eine eigene Spin-Lock-UAV benutzt hatte (weil ich die Tiefen gerade noch als floats speichere). Irgendwo steckt hier aber noch der Wurm drin: Treiber-Resets und sogar zwei Neustarts (wegen eingefrorenem Systems trotz Watchdog) habe ich schon hinter mir.
Code: Alles auswählen
error X3681: ps_5_0 only supports interlocked operations on scalar [cn]int[/cn] or
[cn]uint[/cn] data
Das tut mein Depth Buffering Code oben bereits. Das Problem dabei ist, den ganzen Rest in Abhängigkeit des Tiefentests atomar zu aktualisieren, siehe Code-Snippet.dot hat geschrieben:Wenn ihr eigenes Depth Buffering implementieren wollt, ist ein gängiger Trick, einfach atomicMin() bzw. atomicMax() zu benutzen. Ja, das gibts nicht für floats, aber IEEE floats haben die nette Eigenschaft, dass das relative Ordering ihrer Bitpattern mit dem Ordering der float Werte übereinstimmt... ;)
Code: Alles auswählen
/// Converts the given float into an integer bit representation maintaining relative order.
int asint_ordered(float f)
{
int i = asint(f);
if (i < 0)
i = (1 << 31) - i;
return i;
}
int2 asint_ordered(float2 f) { return int2(asint_ordered(f.x), asint_ordered(f.y)); }
int3 asint_ordered(float3 f) { return int3(asint_ordered(f.x), asint_ordered(f.y), asint_ordered(f.z)); }
int4 asint_ordered(float4 f) { return int4(asint_ordered(f.x), asint_ordered(f.y), asint_ordered(f.z), asint_ordered(f.w)); }
/// Converts the given ordered integer bit representation into the corresponding float.
float asfloat_ordered(int i)
{
if (i < 0)
i = (1 << 31) - i;
return asfloat(i);
}
float2 asfloat_ordered(int2 i) { return float2(asfloat_ordered(i.x), asfloat_ordered(i.y)); }
float3 asfloat_ordered(int3 i) { return float3(asfloat_ordered(i.x), asfloat_ordered(i.y), asfloat_ordered(i.z)); }
float4 asfloat_ordered(int4 i) { return float4(asfloat_ordered(i.x), asfloat_ordered(i.y), asfloat_ordered(i.z), asfloat_ordered(i.w)); }
Ich habe gerade eben testweise sämtliche Synchronisation aus diesem Code genommen:CodingCat hat geschrieben:eXile: Alternativ kannst du natürlich eigene Tiefenpufferung per UAV implementieren, z.B. so:
Das ist mit Sicherheit wesentlich langsamer als ROPs auf Render Targets, erlaubt dir aber atomares Update beliebig vieler Größen in Abhängigkeit des Tiefentests.Code: Alles auswählen
int lastDepthOrLocked = 0; do { // Optional early-out, if beneficial if (depth >= abs(DepthBuffer[pos])) break; // Update depth buffer InterlockedMin(DepthBuffer[pos], depth, lastDepthOrLocked); if (depth < lastDepthOrLocked) { // Lock frame buffer via depth buffer int lastDepth = 0; InterlockedCompareExchange(DepthBuffer[pos], depth, -depth, lastDepth); if (lastDepth == depth) { // Update UAVs // Unlock frame buffer via depth buffer InterlockedExchange(DepthBuffer[pos], depth); } } } while(lastDepthOrLocked < 0);
Code: Alles auswählen
// Optional early-out, if beneficial
if (depth >= abs(DepthBuffer[pos]))
break;
// Update depth buffer
int lastDistBits = 0;
InterlockedMin(DepthBuffer[pos], depth, lastDistBits);
if (depth < lastDistBits)
{
// Update UAVs
}