Files
baldurk c2147b0233 Clarify that most info is unavailable for pixel history on secondaries
* Also fix a potential crash if dynamic rendering is used in secondaries.
2026-03-10 12:40:02 +00:00

1037 lines
40 KiB
Python

import renderdoc as rd
import rdtest
import pprint
import math
from typing import List
def float_value(x): return x.floatValue
def uint_value(x): return x.uintValue
def unknown_stencil(x): return x == -2
# Not a real test, re-used by API-specific tests
class Pixel_History(rdtest.TestCase):
internal = True
demos_test_name = None
def check_capture(self):
# cache some information since our python bindings deep copy actions and this can add up
self.eventCache = {}
for a in self.controller.GetRootActions():
self.populate_eventcache(a, '', [])
self.errored = False
self.batch_base = self.find_action("Batch:", 0)
while self.batch_base is not None:
with rdtest.log.auto_section(self.batch_base.customName):
self.is_secondary = 'Secondary' in self.batch_base.customName
self.batch_test(self.batch_base.eventId)
self.batch_base = self.find_action(
"Batch:", self.batch_base.eventId+1)
if self.errored:
raise rdtest.TestFailureException("Detected problems in pixel history")
def error(self, message):
rdtest.log.error(message)
self.errored = True
def relative_xy(self, x, y):
self.x, self.y = (x >> self.sub.mip, y >> self.sub.mip)
return (self.x, self.y)
def populate_eventcache(self, action: rd.ActionDescription, name: str, parents: List[int]):
hierarchy = parents + [action.eventId]
self.eventCache[action.eventId] = {
'name': name,
'flags': action.flags,
'hierarchy': hierarchy,
}
for child in action.children:
if child.flags & rd.ActionFlags.SetMarker:
name = child.customName
self.populate_eventcache(child, name, hierarchy)
def get_eventname(self, eventId: int) -> str:
return self.eventCache[eventId]['name']
def get_actionflags(self, eventId: int) -> rd.ActionFlags:
return self.eventCache[eventId]['flags']
def get_hierarchy(self, eventId: int) -> List[int]:
return self.eventCache[eventId]['hierarchy']
def batch_test(self, base_eid: int):
test_marker = self.find_action("Simple Test", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
pipe = self.controller.GetPipelineState()
if len(pipe.GetOutputTargets()) > 0:
col = pipe.GetOutputTargets()[0]
else:
col = None
depth = pipe.GetDepthTarget()
self.has_colour = (col is not None and col.resource != rd.ResourceId())
self.has_depth = (depth.resource != rd.ResourceId())
if self.has_colour:
rt = col
tex = col.resource
self.is_depth = False
else:
self.check(self.has_depth)
self.is_depth = True
rt = depth
if self.has_depth:
depth_details = self.get_texture(depth.resource)
self.has_stencil = (depth_details.format.type in [
rd.ResourceFormatType.D16S8, rd.ResourceFormatType.D24S8, rd.ResourceFormatType.D32S8])
else:
self.has_stencil = False
self.tex = tex = rt.resource
tex_details = self.get_texture(tex)
self.epsilon = 1.0/float(1 << tex_details.format.compByteWidth)
sub = rd.Subresource()
if tex_details.arraysize > 1 or tex_details.depth > 1:
sub.slice = rt.firstSlice
if tex_details.mips > 1:
sub.mip = rt.firstMip
self.sub = sub
self.comp = comp = rt.format.compType
def event_name(x: rd.PixelModification):
return self.get_eventname(x.eventId)
value_func = float_value
alpha_value = 2.75 # 1 + ALPHA_ADD=1.75
overflow_value = 100000.0
clear_col = 0.2
if self.comp == rd.CompType.UInt:
alpha_value = 4 # 1 + ALPHA_ADD=3
clear_col = 5 # uint values are shifted by 16, we clear to 80,80,80,160
value_func = uint_value
overflow_value = (1 << 28)-1
def roundToInf(col):
# for half-floats lower inf to the largest representable value since either is valid
if tex_details.format.compType == rd.CompType.Float and tex_details.format.compByteWidth == 2:
return tuple([65504.0 if x == math.inf else x for x in col])
return col
self.fetch_property = {
'_': None, # dynamically skipped test via with_col or with_depth below
'event_name': event_name,
'value': value_func,
'passed': lambda x: x.Passed(),
'culled': lambda x: x.backfaceCulled,
'depth_test_failed': lambda x: x.depthTestFailed,
'depth_clipped': lambda x: x.depthClipped,
'sample_masked': lambda x: x.sampleMasked,
'depth_bounds_failed': lambda x: x.depthBoundsFailed,
'scissor_clipped': lambda x: x.scissorClipped,
'stencil_test_failed': lambda x: x.stencilTestFailed,
'shader_discarded': lambda x: x.shaderDiscarded,
'shader_out_col': lambda x: value_func(x.shaderOut.col),
'shader_out_depth': lambda x: x.shaderOut.depth,
'pre_mod_col': lambda x: value_func(x.preMod.col),
'post_mod_col': lambda x: roundToInf(value_func(x.postMod.col)),
'pre_mod_depth': lambda x: x.preMod.depth,
'post_mod_depth': lambda x: x.postMod.depth,
'post_mod_stencil': lambda x: x.postMod.stencil,
'primitive_id': lambda x: x.primitiveID,
'unknown_post_mod_stencil': lambda x: unknown_stencil(x.postMod.stencil),
'unboundPS': lambda x: x.unboundPS,
'directWrite': lambda x: x.directShaderWrite,
}
def with_depth(x):
return x if self.has_depth else '_'
def with_stencil(x):
return x if self.has_stencil else '_'
def fmt_adjusted(r, g, b, a):
if tex_details.format.compType == rd.CompType.UInt:
r = int(r * 16)
g = int(g * 16)
b = int(b * 16)
a = int(a * 16)
return (r, g, b, a)
def fmt_clamped(r, g, b, a):
r, g, b, a = fmt_adjusted(r, g, b, a)
if tex_details.format.compType == rd.CompType.UNorm:
r = max(0, min(1, r))
g = max(0, min(1, g))
b = max(0, min(1, b))
a = max(0, min(1, a))
elif tex_details.format.compType == rd.CompType.Float and tex_details.format.compByteWidth == 2:
r = max(-65504, min(65504, r))
g = max(-65504, min(65504, g))
b = max(-65504, min(65504, b))
a = max(-65504, min(65504, a))
elif tex_details.format.compType == rd.CompType.UInt:
maxval = (1 << (8*tex_details.format.compByteWidth))-1
r = max(0, min(maxval, r))
b = max(0, min(maxval, b))
a = max(0, min(maxval, a))
g = max(0, min(maxval, g))
return r, g, b, a
# can't reliably get colours, depths, or primitive IDs on secondaries
if self.is_secondary:
self.fetch_property['pre_mod_col'] = None
self.fetch_property['post_mod_col'] = None
self.fetch_property['shader_out_col'] = None
self.fetch_property['pre_mod_depth'] = None
self.fetch_property['shader_out_depth'] = None
self.fetch_property['post_mod_depth'] = None
self.fetch_property['post_mod_stencil'] = None
self.fetch_property['unknown_post_mod_stencil'] = None
self.fetch_property['primitive_id'] = None
if not self.has_colour:
self.fetch_property['pre_mod_col'] = None
self.fetch_property['post_mod_col'] = None
self.fetch_property['shader_out_col'] = None
if not self.has_depth:
self.fetch_property['depth_test_failed'] = None
self.fetch_property['depth_clipped'] = None
self.fetch_property['depth_bounds_failed'] = None
self.fetch_property['stencil_test_failed'] = None
self.fetch_property['pre_mod_depth'] = None
self.fetch_property['shader_out_depth'] = None
self.fetch_property['post_mod_depth'] = None
self.fetch_property['post_mod_stencil'] = None
self.fetch_property['unknown_post_mod_stencil'] = None
# don't check stencil if there's no stencil channel
if not self.has_stencil:
self.fetch_property['post_mod_stencil'] = None
self.fetch_property['unknown_post_mod_stencil'] = None
# check if depth bounds is supportted - D3D11 does not
has_depth_bounds = self.find_action('Depth Bounds Prep') is not None
x, y = self.relative_xy(110, 100)
rdtest.log.print("Testing Unbound PS {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Unbound Shader',
'passed': True,
'unboundPS': True,
'primitive_id': 0,
'post_mod_stencil': 0x33
},
{
'event_name': 'Stencil Write',
'passed': True,
'primitive_id': 0,
'post_mod_stencil': 0x55
},
]
self.check_events(events, modifs)
x, y = self.relative_xy(170, 140)
rdtest.log.print("Testing Stencil failure {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Unbound Shader',
'passed': True,
'unboundPS': True,
'primitive_id': 0
},
{
'event_name': 'Stencil Write',
'passed': True
},
{
'event_name': 'Background',
'depth_test_failed': True,
with_depth('post_mod_col'): fmt_clamped(1, 0, 0, alpha_value)
},
{
'event_name': 'Simple Test',
with_stencil('stencil_test_failed'): True
},
]
self.check_events(events, modifs)
x, y = self.relative_xy(170, 160)
rdtest.log.print("Testing Depth failure {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Depth Write',
'passed': True
},
{
'event_name': 'Background',
'depth_test_failed': True
},
{
'event_name': 'Cull Front',
'culled': True
},
{
'event_name': 'Simple Test',
'depth_test_failed': True
},
]
self.check_events(events, modifs)
x, y = self.relative_xy(150, 250)
rdtest.log.print("Testing Discard {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Background',
'shader_discarded': True
},
]
self.check_events(events, modifs)
x, y = self.relative_xy(330, 145)
rdtest.log.print("Testing Primitive ID {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Simple Test',
'passed': True,
'primitive_id': 3,
'shader_out_col': fmt_adjusted(0, 0, 0, alpha_value)
},
]
self.check_events(events, modifs)
x, y = self.relative_xy(340, 145)
rdtest.log.print("Testing Depth Clip {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Simple Test',
'passed': False,
'depth_clipped': True
},
]
self.check_events(events, modifs)
# if depth bounds test isn't supported these draws won't be emitted
if has_depth_bounds:
x, y = self.relative_xy(330, 102)
rdtest.log.print("Testing Depth Bounds Pass {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Depth Bounds Prep',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(1, 0, 0, alpha_value)
},
{
'event_name': 'Depth Bounds Clip',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(0, 1, 0, alpha_value)
},
]
self.check_events(events, modifs)
# slightly precise X to ensure it still picks the right edge of the triangle on mip version
x, y = self.relative_xy(318, 102)
rdtest.log.print("Testing Depth Bounds Fail {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Depth Bounds Prep',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(1, 0, 0, alpha_value)
},
{
'event_name': 'Depth Bounds Clip',
with_depth('passed'): False,
'depth_bounds_failed': True
},
]
self.check_events(events, modifs)
x, y = self.relative_xy(346, 102)
rdtest.log.print("Testing Depth Bounds Fail {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Depth Bounds Prep',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(1, 0, 0, alpha_value)
},
{
'event_name': 'Depth Bounds Clip',
with_depth('passed'): False,
'depth_bounds_failed': True
},
]
self.check_events(events, modifs)
rdtest.log.print("Testing stencil/scissor checks")
test_marker = self.find_action("Stencil Mask", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(106, 248)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Background',
'passed': True
},
{
'event_name': 'Scissor Fail',
'scissor_clipped': True
},
{
'event_name': 'Scissor Pass',
'passed': True,
'shader_out_col': fmt_adjusted(0, 1, 0, alpha_value),
'post_mod_col': fmt_clamped(0, 1, 0, alpha_value)
},
{
'event_name': 'Stencil Ref',
'passed': True,
'shader_out_col': fmt_adjusted(0, 0, 1, alpha_value),
'post_mod_col': fmt_clamped(0, 0, 1, alpha_value)
},
{
'event_name': 'Stencil Mask',
'passed': True,
'shader_out_col': fmt_adjusted(0, 1, 1, alpha_value),
'post_mod_col': fmt_clamped(0, 1, 1, alpha_value)
},
]
self.check_events(events, modifs)
rdtest.log.print("Testing depth test for per fragment reporting")
test_marker = self.find_action("Depth Test", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(275, 258)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Background',
'passed': True
},
{
'event_name': 'Depth Test',
'primitive_id': 0,
'depth_test_failed': True,
'shader_out_col': fmt_adjusted(0, 1, 0, alpha_value),
'shader_out_depth': 0.97,
with_depth('post_mod_col'): fmt_clamped(1, 0, 1, alpha_value),
'post_mod_depth': 0.95,
'unknown_post_mod_stencil': True
},
{
'event_name': 'Depth Test',
'passed': True,
'primitive_id': 1,
'depth_test_failed': False,
'shader_out_col': fmt_adjusted(1, 1, 0, alpha_value),
'shader_out_depth': 0.20,
'post_mod_col': fmt_clamped(1, 1, 0, alpha_value),
'post_mod_depth': 0.20,
'unknown_post_mod_stencil': True
},
{
'event_name': 'Depth Test',
'primitive_id': 2,
'depth_test_failed': True,
'shader_out_col': fmt_adjusted(1, 0, 0, alpha_value),
'shader_out_depth': 0.30,
with_depth('post_mod_col'): fmt_clamped(1, 1, 0, alpha_value),
'post_mod_depth': 0.20,
'unknown_post_mod_stencil': True
},
{
'event_name': 'Depth Test',
'passed': True,
'primitive_id': 3,
'depth_test_failed': False,
'shader_out_col': fmt_adjusted(0, 0, 1, alpha_value),
'shader_out_depth': 0.10,
'post_mod_col': fmt_clamped(0, 0, 1, alpha_value),
'post_mod_depth': 0.10,
'unknown_post_mod_stencil': True
},
]
# if we don't have depth bounds testing the final event will pass
if has_depth_bounds:
events.append(
{
'event_name': 'Depth Test',
'primitive_id': 4,
'depth_test_failed': False,
'depth_bounds_failed': True,
'shader_out_col': fmt_adjusted(1, 1, 1, alpha_value),
'shader_out_depth': 0.05,
with_depth('post_mod_col'): fmt_clamped(0, 0, 1, alpha_value),
'post_mod_depth': 0.10,
'unknown_post_mod_stencil': False
})
else:
events.append(
{
'event_name': 'Depth Test',
'passed': True,
'primitive_id': 4,
'depth_test_failed': False,
'shader_out_col': fmt_adjusted(1, 1, 1, alpha_value),
'shader_out_depth': 0.05,
with_depth('post_mod_col'): fmt_clamped(1, 1, 1, alpha_value),
'post_mod_depth': 0.05,
'unknown_post_mod_stencil': False
})
# can't distinguish per-fragment results in secondaries
if self.is_secondary:
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Background',
'passed': True
},
{
'event_name': 'Depth Test',
'post_mod_col': fmt_clamped(0, 0, 1, alpha_value),
},
]
self.check_events(events, modifs)
# For pixel 60, 130 inside the light green triangle which is 300 draws of 1 instance of 1 triangle
rdtest.log.print("Testing Lots of Drawcalls")
test_marker = self.find_action("300 Instances", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(60, 130)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
if not self.is_secondary:
self.check_final_colour(tex, x, y, modifs, sub, comp)
countEvents = 1 + 300
if len(modifs) != countEvents:
self.error("Expected {} events, got {}".format(countEvents, len(modifs)))
self.check_modifs_consistent(modifs)
# For pixel 60, 50 inside the orange triangle which is 1 draws of 300 instances of 1 triangle
rdtest.log.print("Testing Lots of Instances")
x, y = self.relative_xy(60, 50)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
if not self.is_secondary:
self.check_final_colour(tex, x, y, modifs, sub, comp)
countEvents = 1 + 255
# secondaries can't count fragment events, so we only get 1 for the draw
if self.is_secondary:
countEvents = 1 + 1
if len(modifs) != countEvents:
self.error("Expected {} events, got {}".format(countEvents, len(modifs)))
self.check_modifs_consistent(modifs)
rdtest.log.print("Testing Sample colouring")
test_marker = self.find_action("Sample Colouring", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(330, 200)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Sample Colouring',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(1, 0, 0, alpha_value)
},
]
self.check_events(events, modifs)
self.check_modifs_consistent(modifs)
if tex_details.msSamp == 4:
sub.sample = 1
rdtest.log.print(f"Testing sample {sub.sample}")
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Sample Colouring',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(0, 0, 1, alpha_value)
},
]
self.check_events(events, modifs)
self.check_modifs_consistent(modifs)
sub.sample = 2
rdtest.log.print(f"Testing sample {sub.sample}")
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Sample Colouring',
'passed': True,
'primitive_id': 0,
'shader_out_col': fmt_adjusted(0, 1, 1, alpha_value)
},
]
self.check_events(events, modifs)
self.check_modifs_consistent(modifs)
sub.sample = 3
rdtest.log.print(f"Testing sample {sub.sample}")
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True,
},
{
'event_name': 'Sample Colouring',
'passed': False,
'sample_masked': True,
},
]
self.check_events(events, modifs)
self.check_modifs_consistent(modifs)
sub.sample = 0
rdtest.log.print("Testing depth-equal testing")
test_marker = self.find_action("Depth Equal Pass", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(200, 250)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Depth Equal Setup',
'passed': True,
'shader_out_col': fmt_adjusted(0.1, 0.1, 0.1, alpha_value),
'shader_out_depth': 0.1,
with_depth('post_mod_col'): fmt_clamped(0.1, 0.1, 0.1, alpha_value),
},
{
'event_name': 'Background',
with_depth('passed'): False,
'depth_test_failed': True,
},
{
'event_name': 'Depth Equal Fail',
with_depth('passed'): False,
'depth_test_failed': True,
'shader_out_col': fmt_adjusted(0, 0, 0, alpha_value),
'shader_out_depth': 0.1 + 5.0e-4,
with_depth('post_mod_col'): fmt_clamped(0.1, 0.1, 0.1, alpha_value),
},
{
'event_name': 'Depth Equal Pass',
'passed': True,
'shader_out_col': fmt_adjusted(1, 1, 1, alpha_value),
'shader_out_depth': 0.1 + 1e-8,
'post_mod_col': fmt_clamped(1, 1, 1, alpha_value),
},
]
self.check_events(events, modifs)
rdtest.log.print("Testing colour masking")
test_marker = self.find_action("Colour Masked", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(60, 80)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Colour Masked',
'passed': True,
'shader_out_col': fmt_adjusted(3, 3, 3, 2+alpha_value),
'post_mod_col': fmt_clamped(3, 3, clear_col, 1),
},
]
self.check_events(events, modifs)
if self.has_colour:
rdtest.log.print("Testing direct writes")
test_marker = self.find_action("Compute write", base_eid)
# don't have compute writes in secondaries and some tests like D3D MSAA won't have compute writes
if test_marker is not None and not self.is_secondary and base_eid in self.get_hierarchy(test_marker.eventId):
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(225, 85)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Background',
'passed': True
},
{
'event_name': 'Compute write',
'passed': True,
'directWrite': True,
'shader_out_depth': -1,
'post_mod_stencil': -1,
'post_mod_depth': -1,
'post_mod_col': fmt_clamped(3, 3, 3, 9),
},
]
self.check_events(events, modifs)
rdtest.log.print("Testing overflowed writes")
test_marker = self.find_action("Overflowing", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(105, 50)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Overflowing',
'passed': True,
'shader_out_col': fmt_adjusted(overflow_value, 0, 0, alpha_value),
'post_mod_col': fmt_clamped(overflow_value, 0, 0, alpha_value)
},
{
'event_name': 'Overflowing',
'passed': True,
'shader_out_col': fmt_adjusted(0, overflow_value, 0, alpha_value),
'post_mod_col': fmt_clamped(0, overflow_value, 0, alpha_value)
},
{
'event_name': 'Overflowing',
'passed': True,
'shader_out_col': fmt_adjusted(0, 0, overflow_value, alpha_value),
'post_mod_col': fmt_clamped(0, 0, overflow_value, alpha_value)
},
]
# can't distinguish per-fragment results in secondaries
if self.is_secondary:
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'event_name': 'Overflowing',
'passed': True,
'shader_out_col': fmt_adjusted(0, 0, overflow_value, alpha_value),
'post_mod_col': fmt_clamped(0, 0, overflow_value, alpha_value)
},
]
self.check_events(events, modifs)
rdtest.log.print("Testing per-fragment discards")
test_marker = self.find_action("Per-Fragment discarding", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
x, y = self.relative_xy(60, 160)
rdtest.log.print("Testing pixel {}, {}".format(x, y))
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
'passed': 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
if self.is_secondary:
events = [
{
'event_name': 'Begin RenderPass',
'passed': True
},
{
'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),
},
]
self.check_events(events, modifs)
if self.has_colour:
x, y = self.relative_xy(120, 110)
rdtest.log.print("Testing D3D no-output shader {}, {}".format(x, y))
test_marker = self.find_action("No Output Shader", base_eid)
self.controller.SetFrameEvent(test_marker.next.eventId, True)
modifs = self.controller.PixelHistory(tex, x, y, sub, comp)
events = [
{
'event_name': 'Begin RenderPass',
},
{
'event_name': 'Unbound Shader',
},
{
'event_name': 'Stencil Write',
},
{
'event_name': 'No Output Shader',
'passed': True,
'unboundPS': True,
'primitive_id': 0
},
]
self.check_events(events, modifs)
def check_final_colour(self, tex, x, y, modifs: List[rd.PixelModification], sub, comp):
m = modifs[-1]
if self.has_colour:
expected = self.fetch_property['value'](m.postMod.col)
else:
expected = (m.postMod.depth, m.postMod.stencil/255.0, 0, 1)
self.check_pixel_value(tex, x, y, expected, sub=sub, cast=comp)
def check_events(self, events, modifs: List[rd.PixelModification]):
# remove any modifs that didn't happen in the batch we're looking at -
# targets can be reused between batches
modifs = [
m for m in modifs if self.batch_base.eventId in self.get_hierarchy(m.eventId)]
if len(modifs) != len(events):
rdtest.log.print(str([e['event_name'] for e in events]))
rdtest.log.print(str([self.fetch_property['event_name'](m) for m in modifs]))
self.error(f"Expected {len(events)} events got {len(modifs)}")
return
if not self.is_secondary:
self.check_modifs_consistent(modifs)
self.check_final_colour(self.tex, self.x, self.y,
modifs, self.sub, self.comp)
for i, m in enumerate(modifs):
m = modifs[i]
for prop_name, expected in events[i].items():
prop_getter = self.fetch_property[prop_name]
# property disabled, e.g. because colour or depth is not present, or we're in a secondary
if prop_getter is None:
# must have a reason - don't allow tests to skip in the main check
self.check(
not self.has_colour or not self.has_depth or not self.has_stencil or self.is_secondary)
continue
actual = prop_getter(m)
epsilon = self.epsilon
if prop_name == 'shader_out_depth':
# reasonable F32 epsilon close to 1.0
epsilon = 6.0e-8
if not rdtest.value_compare(actual, expected, eps=epsilon):
self.error(
f"eventId {m.eventId}, primitiveID {m.primitiveID}: " +
f"testing {prop_name} on modification {i} expected {expected}, got {actual}")
def check_modifs_consistent(self, modifs: List[rd.PixelModification]):
# postmod of each should match premod of the next
for i in range(len(modifs) - 1):
m = modifs[i]
n = modifs[i + 1]
if not m.postMod.IsValid() or not n.preMod.IsValid():
continue
a = self.fetch_property['value'](m.postMod.col)
b = self.fetch_property['value'](n.preMod.col)
# A fragment event. If we have depth postMod.stencil should be unknown and depth should be consistent
if m.eventId == n.eventId and self.has_depth:
if self.has_stencil and not unknown_stencil(m.postMod.stencil):
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):
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}")
epsilon = self.epsilon
if self.is_depth:
a = (m.postMod.depth, m.postMod.stencil)
b = (n.preMod.depth, n.preMod.stencil)
epsilon = 1.0e-5
if not rdtest.value_compare(a, b, eps=epsilon):
self.error(
f"postmod at EID {m.eventId} primitive {m.primitiveID}: {a} "
f"doesn't match premod at {n.eventId} primitive {n.primitiveID}: {b}")
# if we're missing either colour or depth, or it's a clear (which never is affected by the other even if it's bound),
# check that modifications list that properly
for m in modifs:
if not self.has_colour or (self.get_actionflags(m.eventId) & rd.ActionFlags.ClearDepthStencil):
if not rdtest.value_compare(m.preMod.col.uintValue, (0, 0, 0, 0)):
self.error(f"preMod is not zeroed for missing color at EID {m.eventId}")
if not rdtest.value_compare(m.postMod.col.uintValue, (0, 0, 0, 0)):
self.error(f"postMod is not zeroed for missing color at EID {m.eventId}")
if not rdtest.value_compare(m.shaderOut.col.uintValue, (0, 0, 0, 0)):
self.error(f"shaderOut is not zeroed for missing color at EID {m.eventId}")
if not self.has_depth or (self.get_actionflags(m.eventId) & rd.ActionFlags.ClearColor):
if not rdtest.value_compare(m.preMod.depth, -1.0):
self.error(f"preMod depth is not -1 for missing depth at EID {m.eventId}")
if not rdtest.value_compare(m.preMod.stencil, -1):
self.error(f"preMod stencil is not -1 for missing depth at EID {m.eventId}")
if not rdtest.value_compare(m.postMod.depth, -1.0):
self.error(f"postMod depth is not -1 for missing depth at EID {m.eventId}")
if not rdtest.value_compare(m.postMod.stencil, -1):
self.error(f"postMod stencil is not -1 for missing depth at EID {m.eventId}")
if not rdtest.value_compare(m.shaderOut.depth, -1.0):
self.error(f"shaderOut depth is not -1 for missing depth at EID {m.eventId}")
if not rdtest.value_compare(m.shaderOut.stencil, -1):
self.error(f"shaderOut stencil is not -1 for missing depth at EID {m.eventId}")
# Check that if the test failed, its postmod is the same as premod
for i in range(len(modifs)):
if not m.Passed() and m.preMod.IsValid() and m.postMod.IsValid():
a = self.fetch_property['value'](m.preMod.col)
b = self.fetch_property['value'](m.postMod.col)
epsilon = self.epsilon
if self.is_depth:
a = (m.preMod.depth, m.preMod.stencil)
b = (m.postMod.depth, m.postMod.stencil)
epsilon = 1.0e-5
if a[1] == -2 or b[1] == -2:
a = (a[0], -2)
b = (b[0], -2)
if not rdtest.value_compare(a, b, eps=epsilon):
self.error(
f"postmod at EID {m.eventId} primitive {m.primitiveID}: {b} doesn't match premod: {a}")