mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-10-01 10:05:40 +00:00
* For draws where some fragments discard, D3D12 & Vulkan currently can't distinguish which specific fragments did and didn't discard because they're processed batched in separate callbacks rather than incrementally like D3D11 & GL which can track a discarded offset by doing one stencil count at a time. This means we can't get primitive information or ordering, we only get a list of the passing fragments and a set of failing fragments. * Note that on all APIs we also can't distinguish when instancing is present, because there could be multiple "identical" primitives from different primitives, some of which discard and some don't.
1042 lines
41 KiB
Python
1042 lines
41 KiB
Python
from __future__ import annotations
|
|
import renderdoc as rd
|
|
import rdtest
|
|
import math
|
|
from typing import Any, Callable, Dict, List
|
|
|
|
|
|
def float_value(x: rd.PixelValue): return x.floatValue
|
|
def uint_value(x: rd.PixelValue): return x.uintValue
|
|
def unknown_stencil(x: int): return x == -2
|
|
|
|
PropGetter = Callable[[rd.PixelModification], Any]
|
|
|
|
# Not a real test, re-used by API-specific tests
|
|
|
|
|
|
class Pixel_History(rdtest.TestCase):
|
|
internal = True
|
|
slow_test = 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: Dict[int, Dict[str, Any]] = {}
|
|
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: str):
|
|
rdtest.log.error(message)
|
|
self.errored = True
|
|
|
|
def relative_xy(self, x: int, y: int):
|
|
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.set_event(test_marker.nextAction.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:
|
|
assert 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)
|
|
|
|
self.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
|
|
self.value_func = uint_value
|
|
overflow_value = (1 << 28)-1
|
|
|
|
def roundToInf(col: rdtest.VectorValue):
|
|
# 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: Dict[str, PropGetter | None] = {
|
|
'_': None, # dynamically skipped test via with_col or with_depth below
|
|
'event_name': event_name,
|
|
'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: self.value_func(x.shaderOut.col),
|
|
'shader_out_depth': lambda x: x.shaderOut.depth,
|
|
'pre_mod_col': lambda x: self.value_func(x.preMod.col),
|
|
'post_mod_col': lambda x: roundToInf(self.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: str):
|
|
return x if self.has_depth else '_'
|
|
|
|
def with_stencil(x: str):
|
|
return x if self.has_stencil else '_'
|
|
|
|
def fmt_adjusted(r: int|float, g: int|float, b: int|float, a: int|float):
|
|
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: int|float, g: int|float, b: int|float, a: int|float):
|
|
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
|
|
|
|
events: List[Dict[str, Any]]
|
|
|
|
x, y = self.relative_xy(110, 100)
|
|
rdtest.log.print(f"Testing Unbound PS {x}, {y}")
|
|
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
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
x, y = self.relative_xy(170, 140)
|
|
rdtest.log.print(f"Testing Stencil failure {x}, {y}")
|
|
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
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
x, y = self.relative_xy(170, 160)
|
|
rdtest.log.print(f"Testing Depth failure {x}, {y}")
|
|
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
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
x, y = self.relative_xy(150, 250)
|
|
rdtest.log.print(f"Testing Discard {x}, {y}")
|
|
events = [
|
|
{
|
|
'event_name': 'Begin RenderPass',
|
|
'passed': True
|
|
},
|
|
{
|
|
'event_name': 'Background',
|
|
'shader_discarded': True
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
x, y = self.relative_xy(330, 145)
|
|
rdtest.log.print(f"Testing Primitive ID {x}, {y}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
x, y = self.relative_xy(340, 145)
|
|
rdtest.log.print(f"Testing Depth Clip {x}, {y}")
|
|
events = [
|
|
{
|
|
'event_name': 'Begin RenderPass',
|
|
'passed': True
|
|
},
|
|
{
|
|
'event_name': 'Simple Test',
|
|
'passed': False,
|
|
'depth_clipped': True
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.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(f"Testing Depth Bounds Pass {x}, {y}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.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(f"Testing Depth Bounds Fail {x}, {y}")
|
|
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
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
x, y = self.relative_xy(346, 102)
|
|
rdtest.log.print(f"Testing Depth Bounds Fail {x}, {y}")
|
|
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
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
rdtest.log.print("Testing stencil/scissor checks")
|
|
test_marker = self.find_action("Stencil Mask", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
x, y = self.relative_xy(106, 248)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
rdtest.log.print("Testing depth test for per fragment reporting")
|
|
test_marker = self.find_action("Depth Test", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
x, y = self.relative_xy(275, 258)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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),
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.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.set_event(test_marker.nextAction.eventId, True)
|
|
x, y = self.relative_xy(60, 130)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
|
|
countEvents = 1 + 300
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
if len(history.modifs) != countEvents:
|
|
self.error(f"Expected {countEvents} events, got {len(history.modifs)}")
|
|
|
|
if not self.is_secondary:
|
|
self.check_final_colour(tex, x, y, history.modifs, sub, comp)
|
|
|
|
self.check_modifs_consistent(history.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(f"Testing pixel {x}, {y}")
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
if not self.is_secondary:
|
|
self.check_final_colour(tex, x, y, history.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(history.modifs) != countEvents:
|
|
self.error(f"Expected {countEvents} events, got {len(history.modifs)}")
|
|
self.check_modifs_consistent(history.modifs)
|
|
|
|
rdtest.log.print("Testing Sample colouring")
|
|
test_marker = self.find_action("Sample Colouring", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
x, y = self.relative_xy(330, 200)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
if tex_details.msSamp == 4:
|
|
sub.sample = 1
|
|
rdtest.log.print(f"Testing sample {sub.sample}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
sub.sample = 2
|
|
rdtest.log.print(f"Testing sample {sub.sample}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
sub.sample = 3
|
|
rdtest.log.print(f"Testing sample {sub.sample}")
|
|
events = [
|
|
{
|
|
'event_name': 'Begin RenderPass',
|
|
'passed': True,
|
|
},
|
|
{
|
|
'event_name': 'Sample Colouring',
|
|
'passed': False,
|
|
'sample_masked': True,
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
sub.sample = 0
|
|
|
|
rdtest.log.print("Testing depth-equal testing")
|
|
test_marker = self.find_action("Depth Equal Pass", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
x, y = self.relative_xy(200, 250)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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.5,
|
|
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.5 + 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.5 + 1e-8,
|
|
'post_mod_col': fmt_clamped(1, 1, 1, alpha_value),
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
rdtest.log.print("Testing colour masking")
|
|
test_marker = self.find_action("Colour Masked", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
x, y = self.relative_xy(60, 80)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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),
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.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.set_event(test_marker.nextAction.eventId, True)
|
|
x, y = self.relative_xy(225, 85)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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),
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
rdtest.log.print("Testing overflowed writes")
|
|
test_marker = self.find_action("Overflowing", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
x, y = self.relative_xy(105, 50)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
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)
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
rdtest.log.print("Testing per-fragment discards")
|
|
test_marker = self.find_action("Per-Fragment discarding", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
x, y = self.relative_xy(60, 160)
|
|
rdtest.log.print(f"Testing pixel {x}, {y}")
|
|
events = [
|
|
{
|
|
'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,
|
|
},
|
|
]
|
|
# 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),
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
if self.has_colour:
|
|
x, y = self.relative_xy(120, 110)
|
|
rdtest.log.print(f"Testing D3D no-output shader {x}, {y}")
|
|
test_marker = self.find_action("No Output Shader", base_eid)
|
|
self.set_event(test_marker.nextAction.eventId, True)
|
|
|
|
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
|
|
},
|
|
]
|
|
with self.pixel_history(tex, x, y, sub, comp) as history:
|
|
self.check_events(events, history.modifs)
|
|
|
|
def check_final_colour(self, tex: rd.ResourceId, x: int, y: int, modifs: List[rd.PixelModification], sub: rd.Subresource, comp: rd.CompType):
|
|
m = modifs[-1]
|
|
if self.has_colour:
|
|
expected = self.value_func(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: List[Dict[str, Any]], 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]))
|
|
getter = self.fetch_property['event_name']
|
|
assert getter is not None
|
|
rdtest.log.print(str([getter(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
|
|
assert 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.value_func(m.postMod.col)
|
|
b = self.value_func(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, 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}: {n.preMod.depth}")
|
|
|
|
epsilon = self.epsilon
|
|
|
|
if self.is_depth:
|
|
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):
|
|
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)):
|
|
m = modifs[i]
|
|
if not m.Passed() and m.preMod.IsValid() and m.postMod.IsValid():
|
|
a = self.value_func(m.preMod.col)
|
|
b = self.value_func(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}")
|