def get_shading_rate_for_quad(self, tex, x, y): picked = [ self.controller.PickPixel(tex, x + 0, y + 0, rd.Subresource(), rd.CompType.Typeless), self.controller.PickPixel(tex, x + 1, y + 0, rd.Subresource(), rd.CompType.Typeless), self.controller.PickPixel(tex, x + 0, y + 1, rd.Subresource(), rd.CompType.Typeless), self.controller.PickPixel(tex, x + 1, y + 1, rd.Subresource(), rd.CompType.Typeless) ] # all same - 2x2 if all([p.floatValue == picked[0].floatValue for p in picked]): return "2x2" # X same Y diff - 2x1 if (picked[0].floatValue == picked[1].floatValue) and (picked[2].floatValue == picked[3].floatValue) and \ (picked[0].floatValue != picked[2].floatValue): return "2x1" # X diff Y same - 1x2 if (picked[0].floatValue == picked[2].floatValue) and (picked[1].floatValue == picked[3].floatValue) and \ (picked[0].floatValue != picked[1].floatValue): return "1x2" # all different - 1x1 if all([p.floatValue != picked[0].floatValue for p in picked[1:]]): return "1x1" return "?x?"
def depth_target_test(self): test_marker: rd.DrawcallDescription = self.find_draw("Test Begin") self.controller.SetFrameEvent(test_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() rt: rd.BoundResource = pipe.GetDepthTarget() tex = rt.resourceId tex_details = self.get_texture(tex) sub = rd.Subresource() if tex_details.arraysize > 1: sub.slice = rt.firstSlice if tex_details.mips > 1: sub.mip = rt.firstMip begin_renderpass_eid = self.find_draw("Begin RenderPass").next.eventId background_eid = self.find_draw("Background").next.eventId test_eid = self.find_draw("Test Begin").next.eventId x, y = 200, 190 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True], [post_mod_depth, 1.0]], [[event_id, background_eid], [passed, True], [primitive_id, 0], [pre_mod_depth, 1.0], [post_mod_depth, 0.95]], [[event_id, test_eid], [passed, True], [depth_test_failed, False], [primitive_id, 0], [shader_out_depth, 0.5], [post_mod_depth, 0.5]], [[event_id, test_eid], [passed, False], [depth_test_failed, True], [primitive_id, 1], [shader_out_depth, 0.6], [post_mod_depth, 0.5]], ] self.check_events(events, modifs, False)
def check_pixel_value(self, tex: rd.ResourceId, x, y, value, *, sub=None, cast=None, eps=util.FLT_EPSILON): tex_details = self.get_texture(tex) res_details = self.get_resource(tex) if sub is None: sub = rd.Subresource(0, 0, 0) if cast is None: cast = rd.CompType.Typeless if tex_details is not None: if type(x) is float: x = int(((tex_details.width >> sub.mip) - 1) * x) if type(y) is float: y = int(((tex_details.height >> sub.mip) - 1) * y) if cast == rd.CompType.Typeless and tex_details.creationFlags & rd.TextureCategory.SwapBuffer: cast = rd.CompType.UNormSRGB # Reduce epsilon for RGBA8 textures if it's not already reduced if tex_details.format.compByteWidth == 1 and eps == util.FLT_EPSILON: eps = (1.0 / 255.0) picked: rd.PixelValue = self.controller.PickPixel(tex, x, y, sub, cast) picked_value = picked.floatValue if cast == rd.CompType.UInt: picked_value = picked.uintValue elif cast == rd.CompType.SInt: picked_value = picked.intValue if not util.value_compare(picked_value, value, eps): save_data = rd.TextureSave() save_data.resourceId = tex save_data.destType = rd.FileType.PNG save_data.slice.sliceIndex = sub.slice save_data.mip = sub.mip save_data.sample.sampleIndex = sub.sample img_path = util.get_tmp_path('output.png') self.controller.SaveTexture(save_data, img_path) raise TestFailureException( "Picked value {} at {},{} doesn't match expectation of {}". format(picked_value, x, y, value), img_path) name = "Texture" if res_details is not None: name = res_details.name log.success("Picked value at {},{} in {} is as expected".format( x, y, name))
def get_minmax(r: rd.ReplayController): minvals, maxvals = r.GetMinMax(texid, rd.Subresource(), rd.CompType.Typeless) msg = '{} has min {:.4} and max {:.4} in red'.format( texname, minvals.floatValue[0], maxvals.floatValue[0]) mqt.InvokeOntoUIThread(lambda: ctx.Extensions().MessageDialog( msg, "Extension message"))
def multisampled_image_test(self): test_marker: rd.DrawcallDescription = self.find_draw( "Multisampled: test") draw_eid = test_marker.next.eventId self.controller.SetFrameEvent(draw_eid, True) pipe: rd.PipeState = self.controller.GetPipelineState() rt: rd.BoundResource = pipe.GetOutputTargets()[0] sub = rd.Subresource() tex = rt.resourceId tex_details = self.get_texture(tex) if tex_details.arraysize > 1: sub.slice = rt.firstSlice beg_renderpass_eid = self.find_draw( "Multisampled: begin renderpass").next.eventId x, y = 100, 200 sub.sample = 1 rdtest.log.print("Testing pixel {}, {} at sample {}".format( x, y, sub.sample)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, beg_renderpass_eid], [passed, True], [post_mod_col, (0.0, 1.0, 0.0, 1.0)]], [[event_id, draw_eid], [passed, True], [post_mod_col, (0.0, 0.0, 1.0, 1.0)]], ] self.check_events(events, modifs, True) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) sub.sample = 2 rdtest.log.print("Testing pixel {}, {} at sample {}".format( x, y, sub.sample)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, beg_renderpass_eid], [passed, True], [post_mod_col, (0.0, 1.0, 0.0, 1.0)]], [[event_id, draw_eid], [passed, True], [post_mod_col, (0.0, 1.0, 1.0, 1.0)]], ] self.check_events(events, modifs, True) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast)
def check_capture(self): last_action: rd.ActionDescription = self.get_last_action() self.controller.SetFrameEvent(last_action.eventId, True) action: rd.ActionDescription = self.find_action('Duration') min_duration = float(action.customName.split(' = ')[1]) if rd.IsReleaseBuild(): if min_duration >= 15.0: raise rdtest.TestFailureException( "Minimum duration noted {} ms is too high".format( min_duration)) rdtest.log.success( "Minimum duration ({}) is OK".format(min_duration)) else: rdtest.log.print( "Not checking duration ({}) in non-release build".format( min_duration)) resources = self.controller.GetResources() for i in range(8): res: rd.ResourceDescription = [ r for r in resources if r.name == 'Offscreen{}'.format(i) ][0] tex: rd.TextureDescription = self.get_texture(res.resourceId) data = self.controller.GetTextureData(res.resourceId, rd.Subresource(0, 0, 0)) pixels = [ struct.unpack_from("4f", data, 16 * p) for p in range(tex.width * tex.height) ] unique_pixels = list(set(pixels)) if len(unique_pixels) > 2: raise rdtest.TestFailureException( "Too many pixel values found ({})".format( len(unique_pixels))) if (0.0, 0.0, 0.0, 1.0) not in unique_pixels: raise rdtest.TestFailureException( "Didn't find background colour in unique pixels list") unique_pixels.remove((0.0, 0.0, 0.0, 1.0)) if not rdtest.value_compare( (0.8, 0.8, 0.8, 0.4), unique_pixels[0]): raise rdtest.TestFailureException( "Didn't find foreground colour in unique pixels list") rdtest.log.success("{} has correct contents".format(res.name))
def secondary_cmd_test(self): secondary_marker: rd.DrawcallDescription = self.find_draw("Secondary: red and blue") self.controller.SetFrameEvent(secondary_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() rt: rd.BoundResource = pipe.GetOutputTargets()[0] sub = rd.Subresource() tex = rt.resourceId tex_details = self.get_texture(tex) if tex_details.arraysize > 1: sub.slice = rt.firstSlice if tex_details.mips > 1: sub.mip = rt.firstMip sec_beg_renderpass_eid = self.find_draw("Begin RenderPass Secondary").next.eventId background_eid = self.find_draw("Secondary: background").next.eventId culled_eid = self.find_draw("Secondary: culled").next.eventId sec_red_and_blue = self.find_draw("Secondary: red and blue").next.eventId # Test culling x, y = 70, 40 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) events = [ [[event_id, sec_beg_renderpass_eid], [passed, True], [post_mod_col, (0.0, 1.0, 0.0, 1.0)]], [[event_id, background_eid], [passed, True], [pre_mod_col, (0.0, 1.0, 0.0, 1.0)]], [[event_id, culled_eid], [passed, False], [culled, True]], ] self.check_events(events, modifs, True) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) # Blue triangle x, y = 40, 40 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) events = [ [[event_id, sec_beg_renderpass_eid], [passed, True], [post_mod_col, (0.0, 1.0, 0.0, 1.0)]], # This is the first event in the command buffer, should have pre-mod [[event_id, background_eid], [passed, True], [pre_mod_col, (0.0, 1.0, 0.0, 1.0)]], # This is the last event in the command buffer, should have post-mod [[event_id, sec_red_and_blue], [passed, True], [post_mod_col, (0.0, 0.0, 1.0, 1.0)]], ] self.check_events(events, modifs, True) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) # Didn't get post mod for background_eid self.controller.SetFrameEvent(background_eid, True) modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) events = [ [[event_id, sec_beg_renderpass_eid]], # The only event, should have both pre and post mod. [[event_id, background_eid], [passed, True], [pre_mod_col, (0.0, 1.0, 0.0, 1.0)], [post_mod_col, (1.0, 0.0, 1.0, 1.0)]], ] self.check_events(events, modifs, True) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast)
def sample(self, row, col): ret = [] for p in self.points: x = self.view[0] * col + p[0] y = self.view[1] * row + p[1] picked: rd.PixelValue = self.controller.PickPixel( self.tex, x, y, rd.Subresource(0, 0, 0), rd.CompType.Typeless) ret.append( rdtest.value_compare(picked.floatValue, [0.0, 1.0, 1.0, 1.0])) return ret
def check_capture(self): last_draw: rd.DrawcallDescription = self.get_last_draw() self.controller.SetFrameEvent(last_draw.eventId, True) self.check_triangle(out=last_draw.copyDestination) self.check_export(self.capture_filename) draw = self.find_draw("Draw") self.controller.SetFrameEvent(draw.eventId, False) postvs_data = self.get_postvs(draw, rd.MeshDataStage.VSOut, 0, draw.numIndices) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex_var.gl_Position': [-0.5, 0.5, 0.0, 1.0], 'vertOut.pos': [-0.5, 0.5, 0.0, 1.0], 'vertOut.col': [0.0, 1.0, 0.0, 1.0], 'vertOut.uv': [0.0, 0.0, 0.0, 1.0], }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex_var.gl_Position': [0.0, -0.5, 0.0, 1.0], 'vertOut.pos': [0.0, -0.5, 0.0, 1.0], 'vertOut.col': [0.0, 1.0, 0.0, 1.0], 'vertOut.uv': [0.0, 1.0, 0.0, 1.0], }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex_var.gl_Position': [0.5, 0.5, 0.0, 1.0], 'vertOut.pos': [0.5, 0.5, 0.0, 1.0], 'vertOut.col': [0.0, 1.0, 0.0, 1.0], 'vertOut.uv': [1.0, 0.0, 0.0, 1.0], }, } self.check_mesh_data(postvs_ref, postvs_data) # Check that nothing breaks if we call typical enumeration functions on resources for res in self.controller.GetResources(): res: rd.ResourceDescription self.controller.GetShaderEntryPoints(res.resourceId) self.controller.GetUsage(res.resourceId) self.controller.GetBufferData(res.resourceId, 0, 0) self.controller.GetTextureData(res.resourceId, rd.Subresource())
def check_capture(self): last_action: rd.ActionDescription = self.get_last_action() self.controller.SetFrameEvent(last_action.eventId, True) self.check_triangle(out=last_action.copyDestination) self.check_export(self.capture_filename) action = self.find_action("Draw") self.controller.SetFrameEvent(action.eventId, False) postvs_data = self.get_postvs(action, rd.MeshDataStage.VSOut, 0, action.numIndices) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'SV_POSITION': [-0.5, -0.5, 0.0, 1.0], 'COLOR': [0.0, 1.0, 0.0, 1.0], 'TEXCOORD': [0.0, 0.0], }, 1: { 'vtx': 1, 'idx': 1, 'SV_POSITION': [0.0, 0.5, 0.0, 1.0], 'COLOR': [0.0, 1.0, 0.0, 1.0], 'TEXCOORD': [0.0, 1.0], }, 2: { 'vtx': 2, 'idx': 2, 'SV_POSITION': [0.5, -0.5, 0.0, 1.0], 'COLOR': [0.0, 1.0, 0.0, 1.0], 'TEXCOORD': [1.0, 0.0], }, } self.check_mesh_data(postvs_ref, postvs_data) # Check that nothing breaks if we call typical enumeration functions on resources for res in self.controller.GetResources(): res: rd.ResourceDescription self.controller.GetShaderEntryPoints(res.resourceId) self.controller.GetUsage(res.resourceId) self.controller.GetBufferData(res.resourceId, 0, 0) self.controller.GetTextureData(res.resourceId, rd.Subresource())
def check_pixel_value(self, tex: rd.ResourceId, x, y, value, eps=util.FLT_EPSILON): tex_details = self.get_texture(tex) res_details = self.get_resource(tex) if type(x) is float: x = int((tex_details.width-1) * x) if type(y) is float: y = int((tex_details.height-1) * y) picked: rd.PixelValue = self.controller.PickPixel(tex, x, y, rd.Subresource(0, 0, 0), rd.CompType.Typeless) if not util.value_compare(picked.floatValue, value, eps): raise TestFailureException( "Picked value {} at {},{} doesn't match expectation of {}".format(picked.floatValue, x, y, value)) log.success("Picked value at {},{} in {} is as expected".format(x, y, res_details.name))
def get_output_hashes_of_eid(controller, event_id): controller.SetFrameEvent(event_id, True) resource_hashes = dict() pipeline_state = controller.GetPipelineState() color_targets = pipeline_state.GetOutputTargets() depth_targets = [pipeline_state.GetDepthTarget()] for bound_resource in (color_targets + depth_targets): if bound_resource.resourceId == rd.ResourceId.Null(): continue # TODO: Should we check all slices and mips starting from firstMip and firstSlice? subresource = rd.Subresource(bound_resource.firstMip, bound_resource.firstSlice, 0) data = controller.GetTextureData(bound_resource.resourceId, subresource) sha1 = hashlib.sha1() sha1.update(data) subresource_desc = SubresourceDesc(bound_resource.firstMip, bound_resource.firstSlice) resource_hashes[ResourceKey(bound_resource.resourceId, subresource_desc)] = sha1.hexdigest() for stage in range(rd.ShaderStage.Count): rw_resources = pipeline_state.GetReadWriteResources(stage) for rw_resource_array in rw_resources: for bound_rw_resource in rw_resource_array.resources: if bound_rw_resource.resourceId == rd.ResourceId.Null(): continue data = controller.GetBufferData(bound_rw_resource.resourceId, 0, 0) sha1 = hashlib.sha1() sha1.update(data) subresource_desc = SubresourceDesc(0, 0) resource_hashes[ResourceKey(bound_rw_resource.resourceId, subresource_desc)] = sha1.hexdigest() return resource_hashes
def check_capture(self): self.check_textures() # Test render pass attachments draw = self.find_draw("TestStart") rpcol: rd.TextureDescription = self.get_texture( [res for res in self.controller.GetResources() if "RPCol" in res.name][0].resourceId) rpdepth: rd.TextureDescription = self.get_texture( [res for res in self.controller.GetResources() if "RPDepth" in res.name][0].resourceId) self.check(draw is not None) self.controller.SetFrameEvent(draw.next.eventId, True) # At the start they should be cleared for y in range(0, rpcol.height-1, 17): for x in range(0, rpcol.width-1, 17): self.check_pixel_value(rpcol.resourceId, x, y, [0.0, 1.0, 0.0, 1.0]) self.check_pixel_value(rpdepth.resourceId, x, y, [0.4, float(0x40)/float(255), 0.0, 1.0]) rdtest.log.success("Values are correct before the renderpass") draw = self.find_draw("TestMiddle") self.controller.SetFrameEvent(draw.next.eventId, True) for y in range(0, rpcol.height-1, 17): for x in range(0, rpcol.width-1, 17): # if we're in the rect, check for pattern colors if 50 <= x < 125 and 50 <= y < 125: c: rd.PixelValue = self.controller.PickPixel(rpcol.resourceId, x, y, rd.Subresource(), rd.CompType.Typeless) d: rd.PixelValue = self.controller.PickPixel(rpdepth.resourceId, x, y, rd.Subresource(), rd.CompType.Typeless) if not rdtest.value_compare(c.floatValue, [0.0] * 4) and not rdtest.value_compare(c.floatValue, [1000.0] * 4): raise rdtest.TestFailureException( 'middle color has unexpected value at {},{}: {}'.format(x, y, c.floatValue)) if not rdtest.value_compare(d.floatValue[0:2], [0.0] * 2) and not rdtest.value_compare( d.floatValue[0:2], [1.0] * 2): raise rdtest.TestFailureException( 'middle depth has unexpected value at {},{}: {}'.format(x, y, d.floatValue)) else: self.check_pixel_value(rpcol.resourceId, x, y, [0.0, 1.0, 0.0, 1.0]) self.check_pixel_value(rpdepth.resourceId, x, y, [0.4, float(0x40)/float(255), 0.0, 1.0]) middle_col_bytes = self.controller.GetTextureData(rpcol.resourceId, rd.Subresource()) middle_depth_bytes = self.controller.GetTextureData(rpdepth.resourceId, rd.Subresource()) rdtest.log.success("Values are correct in the middle of the renderpass") draw = self.find_draw("TestEnd") self.controller.SetFrameEvent(draw.next.eventId, True) for y in range(0, rpcol.height-1, 17): for x in range(0, rpcol.width-1, 17): # if we're in the rect, check for pattern colors if 50 <= x < 125 and 50 <= y < 125: c: rd.PixelValue = self.controller.PickPixel(rpcol.resourceId, x, y, rd.Subresource(), rd.CompType.Typeless) d: rd.PixelValue = self.controller.PickPixel(rpdepth.resourceId, x, y, rd.Subresource(), rd.CompType.Typeless) if not rdtest.value_compare(c.floatValue, [0.0] * 4) and not rdtest.value_compare(c.floatValue, [1000.0] * 4): raise rdtest.TestFailureException( 'middle color has unexpected value at {},{}: {}'.format(x, y, c.floatValue)) if not rdtest.value_compare(d.floatValue[0:2], [0.0] * 2) and not rdtest.value_compare( d.floatValue[0:2], [1.0] * 2): raise rdtest.TestFailureException( 'middle depth has unexpected value at {},{}: {}'.format(x, y, d.floatValue)) else: self.check_pixel_value(rpcol.resourceId, x, y, [0.0, 1.0, 0.0, 1.0]) self.check_pixel_value(rpdepth.resourceId, x, y, [0.4, float(0x40)/float(255), 0.0, 1.0]) rdtest.log.success("Values are correct after the renderpass") end_col_bytes = self.controller.GetTextureData(rpcol.resourceId, rd.Subresource()) end_depth_bytes = self.controller.GetTextureData(rpdepth.resourceId, rd.Subresource()) self.check(middle_col_bytes != end_col_bytes) self.check(middle_depth_bytes != end_depth_bytes)
def check_capture(self): action = self.get_last_action() self.controller.SetFrameEvent(action.eventId, False) # Should have barycentrics showing the closest vertex for each pixel in the triangle # Without relying on barycentric order, ensure that the three pixels are red, green, and blue pixels = [] picked: rd.PixelValue = self.controller.PickPixel( action.copyDestination, 125, 215, rd.Subresource(), rd.CompType.UNorm) pixels.append(picked.floatValue[0:4]) picked: rd.PixelValue = self.controller.PickPixel( action.copyDestination, 200, 85, rd.Subresource(), rd.CompType.UNorm) pixels.append(picked.floatValue[0:4]) picked: rd.PixelValue = self.controller.PickPixel( action.copyDestination, 285, 215, rd.Subresource(), rd.CompType.UNorm) pixels.append(picked.floatValue[0:4]) if (not (1.0, 0.0, 0.0, 1.0) in pixels) or (not (1.0, 0.0, 0.0, 1.0) in pixels) or ( not (1.0, 0.0, 0.0, 1.0) in pixels): raise rdtest.TestFailureException( "Expected red, green and blue in picked pixels. Got {}".format( pixels)) rdtest.log.success("Picked barycentric values are as expected") # find the cpuMax and gpuMax actions cpuMax = self.find_action("cpuMax") gpuMax = self.find_action("gpuMax") # The values should be identical cpuMax = int(cpuMax.customName[8:]) gpuMax = int(gpuMax.customName[8:]) if cpuMax != gpuMax or cpuMax == 0: raise rdtest.TestFailureException( "captured cpuMax and gpuMax are not equal and positive: {} vs {}" .format(cpuMax, gpuMax)) rdtest.log.success("recorded cpuMax and gpuMax are as expected") outBuf = self.get_resource_by_name("outBuf") data = self.controller.GetBufferData(outBuf.resourceId, 0, 8) replayedGpuMax = struct.unpack("Q", data)[0] if replayedGpuMax != gpuMax: raise rdtest.TestFailureException( "captured gpuMax and replayed gpuMax are not equal: {} vs {}". format(gpuMax, replayedGpuMax)) rdtest.log.success("replayed gpuMax is as expected") cs = self.get_resource_by_name("cs") pipe = rd.ResourceId() refl: rd.ShaderReflection = self.controller.GetShader( pipe, cs.resourceId, rd.ShaderEntryPoint("main", rd.ShaderStage.Compute)) self.check(len(refl.readWriteResources) == 2) self.check([rw.name for rw in refl.readWriteResources] == ["inUAV", "outUAV"]) disasm = self.controller.DisassembleShader(pipe, refl, "") if "amd_u64_atomic" not in disasm: raise rdtest.TestFailureException( "Didn't find expected AMD opcode in disassembly: {}".format( disasm)) rdtest.log.success("compute shader disassembly is as expected") if refl.debugInfo.debuggable: self.controller.SetFrameEvent( self.find_action("Dispatch").eventId, False) trace: rd.ShaderDebugTrace = self.controller.DebugThread((0, 0, 0), (0, 0, 0)) if trace.debugger is None: self.controller.FreeTrace(trace) raise rdtest.TestFailureException( "Couldn't debug compute shader") cycles, variables = self.process_trace(trace) if cycles < 3: raise rdtest.TestFailureException( "Compute shader has too few cycles {}".format(cycles)) else: raise rdtest.TestFailureException( "Compute shader is listed as non-debuggable: {}".format( refl.debugInfo.debugStatus)) rdtest.log.success("compute shader debugged successfully")
def pixel_debug(self, draw: rd.DrawcallDescription): pipe: rd.PipeState = self.controller.GetPipelineState() if pipe.GetShader(rd.ShaderStage.Pixel) == rd.ResourceId.Null(): rdtest.log.print("No pixel shader bound at {}: {}".format(draw.eventId, draw.name)) return if len(pipe.GetOutputTargets()) == 0 and pipe.GetDepthTarget().resourceId == rd.ResourceId.Null(): rdtest.log.print("No render targets bound at {}: {}".format(draw.eventId, draw.name)) return if not (draw.flags & rd.DrawFlags.Drawcall): rdtest.log.print("{}: {} is not a debuggable drawcall".format(draw.eventId, draw.name)) return viewport = pipe.GetViewport(0) # TODO, query for some pixel this drawcall actually touched. x = int(random.random()*viewport.width + viewport.x) y = int(random.random()*viewport.height + viewport.y) target = rd.ResourceId.Null() if len(pipe.GetOutputTargets()) > 0: valid_targets = [o.resourceId for o in pipe.GetOutputTargets() if o.resourceId != rd.ResourceId.Null()] rdtest.log.print("Valid targets at {} are {}".format(draw.eventId, valid_targets)) if len(valid_targets) > 0: target = valid_targets[int(random.random()*len(valid_targets))] if target == rd.ResourceId.Null(): target = pipe.GetDepthTarget().resourceId if target == rd.ResourceId.Null(): rdtest.log.print("No targets bound! Can't fetch history at {}".format(draw.eventId)) return rdtest.log.print("Fetching history for %d,%d on target %s" % (x, y, str(target))) history = self.controller.PixelHistory(target, x, y, rd.Subresource(0, 0, 0), rd.CompType.Typeless) rdtest.log.success("Pixel %d,%d has %d history events" % (x, y, len(history))) lastmod: rd.PixelModification = None for i in range(len(history)-1, 0, -1): mod = history[i] draw = self.find_draw('', mod.eventId) rdtest.log.print(" hit %d at %d is %s (%s)" % (i, mod.eventId, draw.name, str(draw.flags))) if draw is None or not (draw.flags & rd.DrawFlags.Drawcall): continue lastmod = history[i] rdtest.log.print("Got a hit on a drawcall at event %d" % lastmod.eventId) if mod.sampleMasked or mod.backfaceCulled or mod.depthClipped or mod.viewClipped or mod.scissorClipped or mod.depthTestFailed or mod.stencilTestFailed: rdtest.log.print("This hit failed, looking for one that passed....") lastmod = None continue break if lastmod is not None: rdtest.log.print("Debugging pixel {},{} @ {}, primitive {}".format(x, y, lastmod.eventId, lastmod.primitiveID)) self.controller.SetFrameEvent(lastmod.eventId, True) trace = self.controller.DebugPixel(x, y, 0, lastmod.primitiveID) if draw.outputs[0] == rd.ResourceId.Null(): rdtest.log.success('Successfully debugged pixel in {} cycles, skipping result check due to no output'.format(len(trace.states))) elif draw.numInstances == 1: lastState: rd.ShaderDebugState = trace.states[-1] output_index = [o.resourceId for o in self.controller.GetPipelineState().GetOutputTargets()].index(target) rdtest.log.print("At event {} the target is index {}".format(lastmod.eventId, output_index)) debugged: rd.ShaderVariable = lastState.outputs[output_index] debuggedValue = [debugged.value.f.x, debugged.value.f.y, debugged.value.f.z, debugged.value.f.w] if not rdtest.value_compare(lastmod.shaderOut.col.floatValue, [debugged.value.f.x, debugged.value.f.y, debugged.value.f.z, debugged.value.f.w]): raise rdtest.TestFailureException("Debugged value {}: {} doesn't match history shader output {}".format(debugged.name, debuggedValue, lastmod.shaderOut.col.floatValue)) rdtest.log.success('Successfully debugged pixel in {} cycles, result matches'.format(len(trace.states))) else: rdtest.log.success('Successfully debugged pixel in {} cycles, skipping result check due to instancing'.format(len(trace.states))) self.controller.SetFrameEvent(draw.eventId, True)
def check_capture(self): # Jump to the draw draw = self.find_draw("Draw") self.controller.SetFrameEvent(draw.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() failed = False # Loop over every test rdtest.log.begin_section("General tests") for test in range(draw.numInstances): # Debug the shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel(4 * test, 0, rd.ReplayController.NoPreference, rd.ReplayController.NoPreference) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) try: self.check_pixel_value(pipe.GetOutputTargets()[0].resourceId, 4 * test, 0, debugged.value.f32v[0:4]) except rdtest.TestFailureException as ex: failed = True rdtest.log.error("Test {} did not match. {}".format(test, str(ex))) continue finally: self.controller.FreeTrace(trace) rdtest.log.success("Test {} matched as expected".format(test)) rdtest.log.end_section("General tests") rdtest.log.begin_section("MSAA tests") draw = draw.next self.controller.SetFrameEvent(draw.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() for test in range(4): # Debug the shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel(4, 4, test, rd.ReplayController.NoPreference) # Validate that the correct sample index was debugged sampRegister = self.find_input_source_var(trace, rd.ShaderBuiltin.MSAASampleIndex) sampInput = [var for var in trace.inputs if var.name == sampRegister.variables[0].name][0] if sampInput.value.u32v[0] != test: rdtest.log.error("Test {} did not pick the correct sample.".format(test)) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) # Validate the debug output result try: self.check_pixel_value(pipe.GetOutputTargets()[0].resourceId, 4, 4, debugged.value.f32v[0:4], sub=rd.Subresource(0, 0, test)) except rdtest.TestFailureException as ex: failed = True rdtest.log.error("Test {} did not match. {}".format(test, str(ex))) continue rdtest.log.end_section("MSAA tests") if failed: raise rdtest.TestFailureException("Some tests were not as expected") rdtest.log.success("All tests matched")
def check_test(self, fmt_name: str, name: str, test_mode: int): pipe: rd.PipeState = self.controller.GetPipelineState() image_view = (test_mode != Texture_Zoo.TEST_CAPTURE) if image_view: bound_res: rd.BoundResource = pipe.GetOutputTargets()[0] else: bound_res: rd.BoundResource = pipe.GetReadOnlyResources( rd.ShaderStage.Pixel)[0].resources[0] texs = self.controller.GetTextures() for t in texs: self.textures[t.resourceId] = t tex_id: rd.ResourceId = bound_res.resourceId tex: rd.TextureDescription = self.textures[tex_id] comp_type: rd.CompType = tex.format.compType if bound_res.typeCast != rd.CompType.Typeless: comp_type = bound_res.typeCast # When not running proxied, save non-typecasted textures to disk if not image_view and not self.proxied and ( tex.format.compType == comp_type or tex.format.type == rd.ResourceFormatType.D24S8 or tex.format.type == rd.ResourceFormatType.D32S8): save_data = rd.TextureSave() save_data.resourceId = tex_id save_data.destType = rd.FileType.DDS save_data.sample.mapToArray = True self.textures[self.filename] = tex path = rdtest.get_tmp_path(self.filename + '.dds') success: bool = self.controller.SaveTexture(save_data, path) if not success: try: os.remove(path) except Exception: pass save_data.destType = rd.FileType.PNG save_data.slice.sliceIndex = 0 save_data.sample.sampleIndex = 0 path = path.replace('.dds', '.png') if comp_type == rd.CompType.UInt: save_data.comp.blackPoint = 0.0 save_data.comp.whitePoint = 255.0 elif comp_type == rd.CompType.SInt: save_data.comp.blackPoint = -255.0 save_data.comp.whitePoint = 0.0 elif comp_type == rd.CompType.SNorm: save_data.comp.blackPoint = -1.0 save_data.comp.whitePoint = 0.0 success: bool = self.controller.SaveTexture(save_data, path) if not success: try: os.remove(path) except Exception: pass value0 = [] comp_count = tex.format.compCount # When viewing PNGs only compare the components that the original texture had if test_mode == Texture_Zoo.TEST_PNG: comp_count = self.textures[self.filename] tex.msSamp = 0 tex.arraysize = 1 tex.depth = 1 self.fake_msaa = 'MSAA' in name elif test_mode == Texture_Zoo.TEST_DDS: tex.arraysize = self.textures[self.filename].arraysize tex.msSamp = self.textures[self.filename].msSamp self.fake_msaa = 'MSAA' in name # HACK: We don't properly support BGRX, so just drop the alpha channel. We can't set this to compCount = 3 # internally because that's a 24-bit format with no padding... if 'B8G8R8X8' in fmt_name: comp_count = 3 # Completely ignore the alpha for BC1, our encoder doesn't pay attention to it if tex.format.type == rd.ResourceFormatType.BC1: comp_count = 3 # Calculate format-appropriate epsilon eps_significand = 1.0 # Account for the sRGB curve by more generous epsilon if comp_type == rd.CompType.UNormSRGB: eps_significand = 2.5 # Similarly SNorm essentially loses a bit of accuracy due to us only using negative values elif comp_type == rd.CompType.SNorm: eps_significand = 2.0 if tex.format.type == rd.ResourceFormatType.R4G4B4A4 or tex.format.type == rd.ResourceFormatType.R4G4: eps = (eps_significand / 15.0) elif rd.ResourceFormatType.BC1 <= tex.format.type <= rd.ResourceFormatType.BC3: eps = (eps_significand / 15.0) # 4-bit precision in some channels elif tex.format.type == rd.ResourceFormatType.R5G5B5A1 or tex.format.type == rd.ResourceFormatType.R5G6B5: eps = (eps_significand / 31.0) elif tex.format.type == rd.ResourceFormatType.R11G11B10: eps = (eps_significand / 31.0) # 5-bit mantissa in blue elif tex.format.type == rd.ResourceFormatType.R9G9B9E5: eps = ( eps_significand / 63.0 ) # we have 9 bits of data, but might lose 2-3 due to shared exponent elif tex.format.type == rd.ResourceFormatType.BC6 and tex.format.compType == rd.CompType.SNorm: eps = (eps_significand / 63.0 ) # Lose a bit worth of precision for the signed version elif rd.ResourceFormatType.BC4 <= tex.format.type <= rd.ResourceFormatType.BC7: eps = (eps_significand / 127.0) elif tex.format.compByteWidth == 1: eps = (eps_significand / 255.0) elif comp_type == rd.CompType.Depth and tex.format.compCount == 2: eps = (eps_significand / 255.0) # stencil is only 8-bit elif tex.format.type == rd.ResourceFormatType.A8: eps = (eps_significand / 255.0) elif tex.format.type == rd.ResourceFormatType.R10G10B10A2: eps = (eps_significand / 1023.0) else: # half-floats have 11-bit mantissa. This epsilon is tight enough that we can be sure # any remaining errors are implementation inaccuracy and not our bug eps = (eps_significand / 2047.0) for mp in range(tex.mips): for sl in range(max(tex.arraysize, max(1, tex.depth >> mp))): z = 0 if tex.depth > 1: z = sl for sm in range(tex.msSamp): for x in range(max(1, tex.width >> mp)): for y in range(max(1, tex.height >> mp)): picked: rd.PixelValue = self.pick( tex_id, x, y, self.sub(mp, sl, sm), comp_type) # each 3D slice cycles the x. This only affects the primary diagonal offs_x = (x + z) % max(1, tex.width >> mp) # The diagonal inverts the colors inverted = (offs_x != y) # second slice adds a coarse checkerboard pattern of inversion if tex.arraysize > 1 and sl == 1 and ( (int(x / 2) % 2) != (int(y / 2) % 2)): inverted = not inverted if comp_type == rd.CompType.UInt or comp_type == rd.CompType.SInt: expected = [10, 40, 70, 100] if inverted: expected = list(reversed(expected)) expected = [ c + 10 * (sm + mp) for c in expected ] if comp_type == rd.CompType.SInt: picked = picked.intValue else: picked = picked.uintValue elif (tex.format.type == rd.ResourceFormatType.D16S8 or tex.format.type == rd.ResourceFormatType.D24S8 or tex.format.type == rd.ResourceFormatType.D32S8): # depth/stencil is a bit special expected = [0.1, 10, 100, 0.85] if inverted: expected = list(reversed(expected)) expected[0] += 0.075 * (sm + mp) expected[1] += 10 * (sm + mp) # Normalise stencil value expected[1] = expected[1] / 255.0 picked = picked.floatValue else: expected = [0.1, 0.35, 0.6, 0.85] if inverted: expected = list(reversed(expected)) expected = [ c + 0.075 * (sm + mp) for c in expected ] picked = picked.floatValue # SNorm/SInt is negative if comp_type == rd.CompType.SNorm or comp_type == rd.CompType.SInt: expected = [-c for c in expected] # BGRA textures have a swizzle applied if tex.format.BGRAOrder(): expected[0:3] = reversed(expected[0:3]) # alpha channel in 10:10:10:2 has extremely low precision, and the ULP requirements mean # we basically can't trust anything between 0 and 1 on float formats. Just round in that # case as it still lets us differentiate between alpha 0.0-0.5 and 0.5-1.0 if tex.format.type == rd.ResourceFormatType.R10G10B10A2: if comp_type == rd.CompType.UInt: expected[3] = min(3, expected[3]) else: expected[3] = round(expected[3]) * 1.0 picked[3] = round(picked[3]) * 1.0 # Handle 1-bit alpha if tex.format.type == rd.ResourceFormatType.R5G5B5A1: expected[ 3] = 1.0 if expected[3] >= 0.5 else 0.0 picked[3] = 1.0 if picked[3] >= 0.5 else 0.0 # A8 picked values come out in alpha, but we want to compare against the single channel if tex.format.type == rd.ResourceFormatType.A8: picked[0] = picked[3] # Clamp to number of components in the texture expected = expected[0:comp_count] picked = picked[0:comp_count] if mp == 0 and sl == 0 and sm == 0 and x == 0 and y == 0: value0 = picked # For SRGB textures picked values will come out as linear def srgb2linear(f): if f <= 0.04045: return f / 12.92 else: return ((f + 0.055) / 1.055)**2.4 if comp_type == rd.CompType.UNormSRGB: expected[0:3] = [ srgb2linear(x) for x in expected[0:3] ] if test_mode == Texture_Zoo.TEST_PNG: orig_comp = self.textures[ self.filename].format.compType if orig_comp == rd.CompType.SNorm or orig_comp == rd.CompType.SInt: expected = [1.0 - x for x in expected] if not rdtest.value_compare(picked, expected, eps): raise rdtest.TestFailureException( "At ({},{}) of slice {}, mip {}, sample {} of {} {} got {}. Expected {}" .format(x, y, sl, mp, sm, name, fmt_name, picked, expected)) if not image_view: output_tex = pipe.GetOutputTargets()[0].resourceId # in the test captures pick the output texture, it should be identical to the # (0,0) pixel in slice 0, mip 0, sample 0 view: rd.Viewport = pipe.GetViewport(0) val: rd.PixelValue = self.pick( pipe.GetOutputTargets()[0].resourceId, int(view.x + view.width / 2), int(view.y + view.height / 2), rd.Subresource(), rd.CompType.Typeless) picked = val.floatValue # A8 picked values come out in alpha, but we want to compare against the single channel if tex.format.type == rd.ResourceFormatType.A8: picked[0] = picked[3] # Clamp to number of components in the texture picked = picked[0:comp_count] # Up-convert any non-float expected values to floats value0 = [float(x) for x in value0] # For depth/stencil images, one of either depth or stencil should match if comp_type == rd.CompType.Depth and len(value0) == 2: if picked[0] == 0.0: value0[0] = 0.0 # normalise stencil value if it isn't already if picked[1] > 1.0: picked[1] /= 255.0 elif picked[0] > 1.0: # un-normalised stencil being rendered in red, match against our stencil expectation picked[0] /= 255.0 value0[0] = value0[1] value0[1] = 0.0 else: value0[1] = 0.0 if not rdtest.value_compare(picked, value0, eps): raise rdtest.TestFailureException( "In {} {} Top-left pixel as rendered is {}. Expected {}". format(name, fmt_name, picked, value0))
def check_capture(self): if not self.controller.GetAPIProperties().shaderDebugging: rdtest.log.success("Shader debugging not enabled, skipping test") return failed = False shaderModels = ["sm_5_0", "sm_5_1"] for sm in range(len(shaderModels)): rdtest.log.begin_section(shaderModels[sm] + " tests") # Jump to the action test_marker: rd.ActionDescription = self.find_action( shaderModels[sm]) action = test_marker.next self.controller.SetFrameEvent(action.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() if not pipe.GetShaderReflection( rd.ShaderStage.Pixel).debugInfo.debuggable: rdtest.log.print("Skipping undebuggable shader at {}.".format( action.eventId)) return # Loop over every test for test in range(action.numInstances): # Debug the shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel( 4 * test, 0, rd.ReplayController.NoPreference, rd.ReplayController.NoPreference) cycles, variables = self.process_trace(trace) output = self.find_output_source_var( trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) try: self.check_pixel_value( pipe.GetOutputTargets()[0].resourceId, 4 * test, 0, debugged.value.f32v[0:4]) except rdtest.TestFailureException as ex: failed = True rdtest.log.error("Test {} did not match. {}".format( test, str(ex))) continue finally: self.controller.FreeTrace(trace) rdtest.log.success("Test {} matched as expected".format(test)) rdtest.log.end_section(shaderModels[sm] + " tests") rdtest.log.begin_section("MSAA tests") test_marker: rd.ActionDescription = self.find_action("MSAA") action = test_marker.next self.controller.SetFrameEvent(action.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() for test in range(4): # Debug the shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel( 4, 4, test, rd.ReplayController.NoPreference) # Validate that the correct sample index was debugged sampRegister = self.find_input_source_var( trace, rd.ShaderBuiltin.MSAASampleIndex) sampInput = [ var for var in trace.inputs if var.name == sampRegister.variables[0].name ][0] if sampInput.value.u32v[0] != test: rdtest.log.error( "Test {} did not pick the correct sample.".format(test)) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) # Validate the debug output result try: self.check_pixel_value(pipe.GetOutputTargets()[0].resourceId, 4, 4, debugged.value.f32v[0:4], sub=rd.Subresource(0, 0, test)) except rdtest.TestFailureException as ex: failed = True rdtest.log.error("Test {} did not match. {}".format( test, str(ex))) continue rdtest.log.end_section("MSAA tests") test_marker: rd.ActionDescription = self.find_action("VertexSample") action = test_marker.next self.controller.SetFrameEvent(action.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() # Debug the vertex shader trace: rd.ShaderDebugTrace = self.controller.DebugVertex(0, 0, 0, 0) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.Undefined, 1) debugged = self.evaluate_source_var(output, variables) if not rdtest.value_compare(debugged.value.f32v[0:4], [0.3, 0.5, 0.8, 1.0]): failed = True rdtest.log.error( "Vertex shader color output did not match expectation ({}). {}" .format(str(debugged.value.f32v[0:4]), str(ex))) rdtest.log.success("VertexSample VS was debugged correctly") # Debug the pixel shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel( 51, 51, 0, rd.ReplayController.NoPreference) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) # Validate the debug output result try: self.check_pixel_value(pipe.GetOutputTargets()[0].resourceId, 51, 51, debugged.value.f32v[0:4]) except rdtest.TestFailureException as ex: failed = True rdtest.log.error( "Vertex sample pixel shader output did not match. {}".format( str(ex))) rdtest.log.success("VertexSample PS was debugged correctly") if failed: raise rdtest.TestFailureException( "Some tests were not as expected") test_marker: rd.ActionDescription = self.find_action("Banned") action = test_marker.next self.controller.SetFrameEvent(action.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() # Debug the vertex shader trace: rd.ShaderDebugTrace = self.controller.DebugVertex(0, 0, 0, 0) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.Position, 0) debugged = self.evaluate_source_var(output, variables) if not rdtest.value_compare(debugged.value.f32v[0:4], [-0.5, -0.5, 0.0, 1.0]): failed = True rdtest.log.error( "Banned signature vertex shader position did not match expectation ({}). {}" .format(str(debugged.value.f32v[0:4]), str(ex))) rdtest.log.success("Banned signature VS was debugged correctly") # Debug the pixel shader trace: rd.ShaderDebugTrace = self.controller.DebugPixel( 64, 64, 0, rd.ReplayController.NoPreference) cycles, variables = self.process_trace(trace) output = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) debugged = self.evaluate_source_var(output, variables) # Validate the debug output result try: self.check_pixel_value(pipe.GetOutputTargets()[0].resourceId, 64, 64, debugged.value.f32v[0:4]) except rdtest.TestFailureException as ex: failed = True rdtest.log.error( "Vertex sample pixel shader output did not match. {}".format( str(ex))) rdtest.log.success("Banned signature PS was debugged correctly") if failed: raise rdtest.TestFailureException( "Some tests were not as expected") rdtest.log.success("All tests matched")
def multisampled_image_test(self): test_marker: rd.ActionDescription = self.find_action( "Multisampled: test") action_eid = test_marker.next.eventId self.controller.SetFrameEvent(action_eid, True) pipe: rd.PipeState = self.controller.GetPipelineState() rt: rd.BoundResource = pipe.GetOutputTargets()[0] if self.is_depth: rt: rd.BoundResource = pipe.GetDepthTarget() sub = rd.Subresource() tex = rt.resourceId tex_details = self.get_texture(tex) if tex_details.arraysize > 1: sub.slice = rt.firstSlice beg_renderpass_eid = self.find_action( "Multisampled: begin renderpass").next.eventId x, y = 140, 130 sub.sample = 1 rdtest.log.print("Testing pixel {}, {} at sample {}".format( x, y, sub.sample)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, beg_renderpass_eid], [passed, True], [post_mod_depth, 0.0]], [[event_id, action_eid], [passed, True], [primitive_id, 0], [pre_mod_depth, 0.0], [shader_out_depth, 0.9], [post_mod_depth, 0.9]], [[event_id, action_eid], [passed, True], [primitive_id, 1], [shader_out_depth, 0.95], [post_mod_depth, 0.95]], ] if not self.is_depth: events[0] += [[post_mod_col, (0.0, 1.0, 0.0, 1.0)]] events[1] += [[shader_out_col, (1.0, 0.0, 1.0, 2.75)], [post_mod_col, (1.0, 0.0, 1.0, 1.0)]] events[2] += [[shader_out_col, (0.0, 0.0, 1.0, 2.75)], [post_mod_col, (0.0, 0.0, 1.0, 1.0)]] self.check_events(events, modifs, True) if self.is_depth: self.check_pixel_value( tex, x, y, [ modifs[-1].postMod.depth, float(modifs[-1].postMod.stencil) / 255.0, 0.0, 1.0 ], sub=sub, cast=rt.typeCast) else: self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) sub.sample = 2 rdtest.log.print("Testing pixel {}, {} at sample {}".format( x, y, sub.sample)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, beg_renderpass_eid], [passed, True], [post_mod_depth, 0.0]], [[event_id, action_eid], [passed, True], [primitive_id, 0], [pre_mod_depth, 0.0], [shader_out_depth, 0.9], [post_mod_depth, 0.9]], [[event_id, action_eid], [passed, True], [primitive_id, 1], [shader_out_depth, 0.95], [post_mod_depth, 0.95]], ] if not self.is_depth: events[0] += [[post_mod_col, (0.0, 1.0, 0.0, 1.0)]] events[1] += [[shader_out_col, (1.0, 0.0, 1.0, 2.75)], [post_mod_col, (1.0, 0.0, 1.0, 1.0)]] events[2] += [[shader_out_col, (0.0, 1.0, 1.0, 2.75)], [post_mod_col, (0.0, 1.0, 1.0, 1.0)]] self.check_events(events, modifs, True) if self.is_depth: self.check_pixel_value( tex, x, y, [ modifs[-1].postMod.depth, float(modifs[-1].postMod.stencil) / 255.0, 0.0, 1.0 ], sub=sub, cast=rt.typeCast) else: self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast)
def check_capture_with_controller(self, proxy_api: str): self.controller: rd.ReplayController any_failed = False if proxy_api != '': rdtest.log.print('Running with {} local proxy'.format(proxy_api)) self.proxied = True else: rdtest.log.print('Running on direct replay') self.proxied = False self.out: rd.ReplayOutput = self.controller.CreateOutput( rd.CreateHeadlessWindowingData(100, 100), rd.ReplayOutputType.Texture) for d in self.controller.GetRootActions(): if 'slice tests' in d.customName: for sub in d.children: if sub.flags & rd.ActionFlags.Drawcall: self.controller.SetFrameEvent(sub.eventId, True) pipe = self.controller.GetPipelineState() tex_id = pipe.GetReadOnlyResources( rd.ShaderStage.Pixel)[0].resources[0].resourceId for mip in [0, 1]: for sl in [16, 17, 18]: expected = [0.0, 0.0, 1.0, 1.0] if sl == 17: expected = [0.0, 1.0, 0.0, 1.0] cur_sub = rd.Subresource(mip, sl) comp_type = rd.CompType.Typeless # test that pixel picking sees the right things picked = self.controller.PickPixel( tex_id, 15, 15, cur_sub, comp_type) if not rdtest.value_compare( picked.floatValue, expected): raise rdtest.TestFailureException( "Expected to pick {} at slice {} mip {}, got {}" .format(expected, sl, mip, picked.floatValue)) rdtest.log.success( 'Picked pixel is correct at slice {} mip {}' .format(sl, mip)) # Render output texture a three scales - below 100%, 100%, above 100% tex_display = rd.TextureDisplay() tex_display.resourceId = tex_id tex_display.subresource = cur_sub tex_display.typeCast = comp_type # convert the unorm values to byte values for comparison expected = [ int(a * 255) for a in expected[0:3] ] for scale in [0.9, 1.0, 1.1]: tex_display.scale = scale self.out.SetTextureDisplay(tex_display) self.out.Display() pixels: bytes = self.out.ReadbackOutputTexture( ) actual = [int(a) for a in pixels[0:3]] if not rdtest.value_compare( actual, expected): raise rdtest.TestFailureException( "Expected to display {} at slice {} mip {} scale {}%, got {}" .format(expected, sl, mip, int(scale * 100), actual)) rdtest.log.success( 'Displayed pixel is correct at scale {}% in slice {} mip {}' .format(int(scale * 100), sl, mip)) elif sub.flags & rd.ActionFlags.SetMarker: rdtest.log.print( 'Checking {} for slice display'.format( sub.customName)) continue # Check each region for the tests within if d.flags & rd.ActionFlags.PushMarker: name = '' tests_run = 0 failed = False # Iterate over actions in this region for sub in d.children: sub: rd.ActionDescription if sub.flags & rd.ActionFlags.SetMarker: name = sub.customName # Check this action if sub.flags & rd.ActionFlags.Drawcall: tests_run = tests_run + 1 try: # Set this event as current self.controller.SetFrameEvent(sub.eventId, True) self.filename = (d.customName + '@' + name).replace('->', '_') self.check_test(d.customName, name, Texture_Zoo.TEST_CAPTURE) except rdtest.TestFailureException as ex: failed = any_failed = True rdtest.log.error(str(ex)) if not failed: rdtest.log.success( "All {} texture tests for {} are OK".format( tests_run, d.customName)) self.out.Shutdown() self.out = None if not any_failed: if proxy_api != '': rdtest.log.success( 'All textures are OK with {} as local proxy'.format( proxy_api)) else: rdtest.log.success("All textures are OK on direct replay") else: raise rdtest.TestFailureException( "Some tests were not as expected")
def check_capture(self): id = self.get_last_draw().copyDestination tex_details = self.get_texture(id) self.controller.SetFrameEvent(self.get_last_draw().eventId, True) data = self.controller.GetTextureData(id, rd.Subresource(0, 0, 0)) first_pixel = struct.unpack_from("BBBB", data, 0) val = [255, 0, 255, 255] if not rdtest.value_compare(first_pixel, val): raise rdtest.TestFailureException( "First pixel should be clear color {}, not {}".format( val, first_pixel)) magic_pixel = struct.unpack_from("BBBB", data, (50 * tex_details.width + 320) * 4) # allow 127 or 128 for alpha val = [0, 0, 255, magic_pixel[3]] if not rdtest.value_compare(magic_pixel, val) or magic_pixel[3] not in [127, 128]: raise rdtest.TestFailureException( "Pixel @ 320,50 should be blue: {}, not {}".format( val, magic_pixel)) rdtest.log.success("Decoded pixels from texture data are correct") img_path = rdtest.get_tmp_path('preserved_alpha.png') self.controller.SetFrameEvent(self.get_last_draw().eventId, True) save_data = rd.TextureSave() save_data.resourceId = id save_data.destType = rd.FileType.PNG save_data.alpha = rd.AlphaMapping.Discard # this should not discard the alpha self.controller.SaveTexture(save_data, img_path) data = rdtest.png_load_data(img_path) magic_pixel = struct.unpack_from("BBBB", data[-1 - 50], 320 * 4) val = [0, 0, 255, magic_pixel[3]] if not rdtest.value_compare(magic_pixel, val) or magic_pixel[3] not in [127, 128]: raise rdtest.TestFailureException( "Pixel @ 320,50 should be blue: {}, not {}".format( val, magic_pixel)) draw = self.find_draw("Draw") self.controller.SetFrameEvent(draw.eventId, False) postvs_data = self.get_postvs(draw, rd.MeshDataStage.VSOut, 0, draw.numIndices) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_Position': [-0.5, -0.5, 0.0, 1.0], 'v2fcol': [0.0, 1.0, 0.0, 1.0], }, 1: { 'vtx': 1, 'idx': 1, 'gl_Position': [0.0, 0.5, 0.0, 1.0], 'v2fcol': [0.0, 1.0, 0.0, 1.0], }, 2: { 'vtx': 2, 'idx': 2, 'gl_Position': [0.5, -0.5, 0.0, 1.0], 'v2fcol': [0.0, 1.0, 0.0, 1.0], }, } self.check_mesh_data(postvs_ref, postvs_data) results = self.controller.FetchCounters([ rd.GPUCounter.RasterizedPrimitives, rd.GPUCounter.VSInvocations, rd.GPUCounter.FSInvocations ]) results = [r for r in results if r.eventId == draw.eventId] if len(results) != 3: raise rdtest.TestFailureException( "Expected 3 results, got {} results".format(len(results))) for r in results: r: rd.CounterResult val = r.value.u32 if r.counter == rd.GPUCounter.RasterizedPrimitives: if not rdtest.value_compare(val, 1): raise rdtest.TestFailureException( "RasterizedPrimitives result {} is not as expected". format(val)) else: rdtest.log.success( "RasterizedPrimitives result is as expected") elif r.counter == rd.GPUCounter.VSInvocations: if not rdtest.value_compare(val, 3): raise rdtest.TestFailureException( "VSInvocations result {} is not as expected".format( val)) else: rdtest.log.success("VSInvocations result is as expected") elif r.counter == rd.GPUCounter.FSInvocations: if val < int(0.1 * tex_details.width * tex_details.height): raise rdtest.TestFailureException( "FSInvocations result {} is not as expected".format( val)) else: rdtest.log.success("FSInvocations result is as expected") else: raise rdtest.TestFailureException( "Unexpected counter result {}".format(r.counter)) rdtest.log.success("Counter data retrieved successfully") draw = self.find_draw("NoScissor") self.check(draw is not None) draw = draw.next pipe: rd.PipeState = self.controller.GetPipelineState() tex = rd.TextureDisplay() tex.overlay = rd.DebugOverlay.Drawcall tex.resourceId = pipe.GetOutputTargets()[0].resourceId out: rd.ReplayOutput = self.controller.CreateOutput( rd.CreateHeadlessWindowingData(100, 100), rd.ReplayOutputType.Texture) out.SetTextureDisplay(tex) out.Display() overlay_id = out.GetDebugOverlayTexID() v = pipe.GetViewport(0) self.check_pixel_value(overlay_id, int(0.5 * v.width), int(0.5 * v.height), [0.8, 0.1, 0.8, 1.0], eps=1.0 / 256.0) out.Shutdown() rdtest.log.success("Overlay color is as expected")
def check_capture(self): out: rd.ReplayOutput = self.controller.CreateOutput(rd.CreateHeadlessWindowingData(100, 100), rd.ReplayOutputType.Texture) self.check(out is not None) test_marker: rd.DrawcallDescription = self.find_draw("Test") self.controller.SetFrameEvent(test_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() col_tex: rd.ResourceId = pipe.GetOutputTargets()[0].resourceId tex = rd.TextureDisplay() tex.resourceId = col_tex # Check the actual output is as expected first. # Background around the outside self.check_pixel_value(col_tex, 0.1, 0.1, [0.2, 0.2, 0.2, 1.0]) self.check_pixel_value(col_tex, 0.8, 0.1, [0.2, 0.2, 0.2, 1.0]) self.check_pixel_value(col_tex, 0.5, 0.95, [0.2, 0.2, 0.2, 1.0]) # Large dark grey triangle self.check_pixel_value(col_tex, 0.5, 0.1, [0.1, 0.1, 0.1, 1.0]) self.check_pixel_value(col_tex, 0.5, 0.9, [0.1, 0.1, 0.1, 1.0]) self.check_pixel_value(col_tex, 0.2, 0.9, [0.1, 0.1, 0.1, 1.0]) self.check_pixel_value(col_tex, 0.8, 0.9, [0.1, 0.1, 0.1, 1.0]) # Red upper half triangle self.check_pixel_value(col_tex, 0.3, 0.4, [1.0, 0.0, 0.0, 1.0]) # Blue lower half triangle self.check_pixel_value(col_tex, 0.3, 0.6, [0.0, 0.0, 1.0, 1.0]) # Floating clipped triangle self.check_pixel_value(col_tex, 335, 140, [0.0, 0.0, 0.0, 1.0]) self.check_pixel_value(col_tex, 340, 140, [0.2, 0.2, 0.2, 1.0]) # Triangle size triangles self.check_pixel_value(col_tex, 200, 51, [1.0, 0.5, 1.0, 1.0]) self.check_pixel_value(col_tex, 200, 65, [1.0, 1.0, 0.0, 1.0]) self.check_pixel_value(col_tex, 200, 79, [0.0, 1.0, 1.0, 1.0]) self.check_pixel_value(col_tex, 200, 93, [0.0, 1.0, 0.0, 1.0]) for overlay in rd.DebugOverlay: if overlay == rd.DebugOverlay.NoOverlay: continue # These overlays are just displaymodes really, not actually separate overlays if overlay == rd.DebugOverlay.NaN or overlay == rd.DebugOverlay.Clipping: continue # We'll test the clear-before-X overlays seperately, for both colour and depth if overlay == rd.DebugOverlay.ClearBeforeDraw or overlay == rd.DebugOverlay.ClearBeforePass: continue rdtest.log.print("Checking overlay {} in main draw".format(str(overlay))) tex.overlay = overlay out.SetTextureDisplay(tex) out.Display() eps = 1.0 / 256.0 overlay_id: rd.ResourceId = out.GetDebugOverlayTexID() # We test a few key spots: # 4 points along the left side of the big triangle, above/in/below its intersection with the tri behind # 4 points outside all triangles # The overlap between the big tri and the bottom tri, and between it and the right backface culled tri # The bottom tri's part that sticks out # The two parts of the backface culled tri that stick out # The depth clipped tri, in and out of clipping # The 4 triangle size test triangles if overlay == rd.DebugOverlay.Drawcall: self.check_pixel_value(overlay_id, 150, 90, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 0.0, 0.0, 0.5], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [0.0, 0.0, 0.0, 0.5], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 0.0, 0.0, 0.5], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [0.0, 0.0, 0.0, 0.5], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 135, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 340, 145, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 51, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [0.8, 0.1, 0.8, 1.0], eps=eps) elif overlay == rd.DebugOverlay.Wireframe: # Wireframe we only test a limited set to avoid hitting implementation variations of line raster # We also have to fudge a little because the lines might land on adjacent pixels x = 142 picked: rd.PixelValue = self.controller.PickPixel(overlay_id, x, 150, rd.Subresource(), rd.CompType.Typeless) if picked.floatValue[3] == 0.0: x = 141 self.check_pixel_value(overlay_id, x, 90, [200.0/255.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, x, 130, [200.0/255.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, x, 160, [200.0/255.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, x, 200, [200.0/255.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [200.0/255.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [200.0/255.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [200.0/255.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [200.0/255.0, 1.0, 0.0, 0.0], eps=eps) y = 149 picked: rd.PixelValue = self.controller.PickPixel(overlay_id, 325, y, rd.Subresource(), rd.CompType.Typeless) if picked.floatValue[3] == 0.0: y = 150 self.check_pixel_value(overlay_id, 325, y, [200.0/255.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 340, y, [200.0/255.0, 1.0, 0.0, 1.0], eps=eps) elif overlay == rd.DebugOverlay.Depth: self.check_pixel_value(overlay_id, 150, 90, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [0.0, 1.0, 0.0, 1.0], eps=eps) # Intersection with lesser depth - depth fail self.check_pixel_value(overlay_id, 150, 160, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [0.0, 1.0, 0.0, 1.0], eps=eps) # Backface culled triangle self.check_pixel_value(overlay_id, 285, 135, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [0.0, 1.0, 0.0, 1.0], eps=eps) # Depth clipped part of tri self.check_pixel_value(overlay_id, 340, 145, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 51, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [0.0, 1.0, 0.0, 1.0], eps=eps) elif overlay == rd.DebugOverlay.Stencil: self.check_pixel_value(overlay_id, 150, 90, [0.0, 1.0, 0.0, 1.0], eps=eps) # Intersection with different stencil - stencil fail self.check_pixel_value(overlay_id, 150, 130, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [0.0, 1.0, 0.0, 1.0], eps=eps) # Backface culled triangle self.check_pixel_value(overlay_id, 285, 135, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [0.0, 1.0, 0.0, 1.0], eps=eps) # Depth clipped part of tri self.check_pixel_value(overlay_id, 340, 145, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 51, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [0.0, 1.0, 0.0, 1.0], eps=eps) elif overlay == rd.DebugOverlay.BackfaceCull: self.check_pixel_value(overlay_id, 150, 90, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [0.0, 1.0, 0.0, 1.0], eps=eps) # Backface culled triangle self.check_pixel_value(overlay_id, 285, 135, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 340, 145, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 51, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [0.0, 1.0, 0.0, 1.0], eps=eps) elif overlay == rd.DebugOverlay.ViewportScissor: # Inside viewport self.check_pixel_value(overlay_id, 50, 50, [0.2*0.7, 0.2*0.7, 0.9*0.7, 0.7*0.7], eps=eps) self.check_pixel_value(overlay_id, 350, 50, [0.2*0.7, 0.2*0.7, 0.9*0.7, 0.7*0.7], eps=eps) self.check_pixel_value(overlay_id, 50, 250, [0.2*0.7, 0.2*0.7, 0.9*0.7, 0.7*0.7], eps=eps) self.check_pixel_value(overlay_id, 350, 250, [0.2*0.7, 0.2*0.7, 0.9*0.7, 0.7*0.7], eps=eps) # Viewport border self.check_pixel_value(overlay_id, 12, 12, [0.1, 0.1, 0.1, 1.0], eps=eps) # Outside viewport (not on scissor border) self.check_pixel_value(overlay_id, 6, 6, [0.0, 0.0, 0.0, 0.0], eps=eps) # Scissor border self.check_pixel_value(overlay_id, 0, 0, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 20, 0, [0.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 40, 0, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 60, 0, [0.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 60, 0, [0.0, 0.0, 0.0, 1.0], eps=eps) elif overlay == rd.DebugOverlay.QuadOverdrawDraw: self.check_pixel_value(overlay_id, 150, 90, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 135, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 340, 145, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 200, 51, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [1.0, 1.0, 1.0, 1.0], eps=eps) elif overlay == rd.DebugOverlay.QuadOverdrawPass: self.check_pixel_value(overlay_id, 150, 90, [1.0, 1.0, 1.0, 1.0], eps=eps) # Do an extra tap here where we overlap with the extreme-background largest triangle, to show that # overdraw self.check_pixel_value(overlay_id, 150, 100, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [2.0, 2.0, 2.0, 2.0], eps=eps) # Two of these have overdraw from the pass due to the large background triangle self.check_pixel_value(overlay_id, 125, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [3.0, 3.0, 3.0, 3.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 285, 135, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 340, 145, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 200, 51, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [2.0, 2.0, 2.0, 2.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [2.0, 2.0, 2.0, 2.0], eps=eps) elif overlay == rd.DebugOverlay.TriangleSizeDraw: eps = 1.0 self.check_pixel_value(overlay_id, 150, 90, [10632.0, 10632.0, 10632.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [10632.0, 10632.0, 10632.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [2128.0, 2128.0, 2128.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [10632.0, 10632.0, 10632.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [2128.0, 2128.0, 2128.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 135, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [531.0, 531.0, 531.0, 531.0], eps=eps) self.check_pixel_value(overlay_id, 340, 145, [0.0, 0.0, 0.0, 0.0], eps=eps) eps = 0.01 self.check_pixel_value(overlay_id, 200, 51, [8.305, 8.305, 8.305, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [5.316, 5.316, 5.316, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [3.0, 3.0, 3.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [1.33, 1.33, 1.33, 1.0], eps=eps) elif overlay == rd.DebugOverlay.TriangleSizePass: eps = 1.0 self.check_pixel_value(overlay_id, 150, 90, [10632.0, 10632.0, 10632.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 130, [3324.0, 3324.0, 3324.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 160, [3324.0, 3324.0, 3324.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 150, 200, [10632.0, 10632.0, 10632.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 125, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 125, 250, [43072.0, 43072.0, 43072.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 60, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 250, 250, [43072.0, 43072.0, 43072.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 175, [2128.0, 2128.0, 2128.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 250, 150, [10632.0, 10632.0, 10632.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 220, 190, [2128.0, 2128.0, 2128.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 285, 135, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 285, 165, [43072.0, 43072.0, 43072.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 330, 145, [531.0, 531.0, 531.0, 531.0], eps=eps) self.check_pixel_value(overlay_id, 340, 145, [0.0, 0.0, 0.0, 0.0], eps=eps) eps = 0.01 self.check_pixel_value(overlay_id, 200, 51, [8.305, 8.305, 8.305, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 65, [5.316, 5.316, 5.316, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 79, [3.0, 3.0, 3.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 93, [1.33, 1.33, 1.33, 1.0], eps=eps) rdtest.log.success("Picked pixels are as expected for {}".format(str(overlay))) # Now check clear-before-X by hand, for colour and for depth depth_tex: rd.ResourceId = pipe.GetDepthTarget().resourceId eps = 1.0/256.0 # Check colour and depth before-hand self.check_pixel_value(col_tex, 250, 250, [0.1, 0.1, 0.1, 1.0], eps=eps) self.check_pixel_value(col_tex, 125, 125, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 125, 175, [0.0, 0.0, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 50, 50, [0.2, 0.2, 0.2, 1.0], eps=eps) self.check_pixel_value(col_tex, 291, 150, [0.977, 0.977, 0.977, 1.0], eps=0.075) self.check_pixel_value(col_tex, 200, 51, [1.0, 0.5, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 65, [1.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 79, [0.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 93, [0.0, 1.0, 0.0, 1.0], eps=eps) eps = 0.001 self.check_pixel_value(depth_tex, 160, 135, [0.9, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 160, 165, [0.0, 0.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 250, 150, [0.5, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 250, 250, [0.95, 0.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 50, 50, [1.0, 0.0/255.0, 0.0, 1.0], eps=eps) # Check clear before pass tex.resourceId = col_tex tex.overlay = rd.DebugOverlay.ClearBeforePass out.SetTextureDisplay(tex) out.Display() eps = 1.0/256.0 self.check_pixel_value(col_tex, 250, 250, [0.1, 0.1, 0.1, 1.0], eps=eps) self.check_pixel_value(col_tex, 125, 125, [1.0, 0.0, 0.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 125, 175, [0.0, 0.0, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 50, 50, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(col_tex, 291, 150, [0.977, 0.977, 0.977, 1.0], eps=0.075) self.check_pixel_value(col_tex, 200, 51, [1.0, 0.5, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 65, [1.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 79, [0.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 93, [0.0, 1.0, 0.0, 1.0], eps=eps) tex.resourceId = depth_tex tex.overlay = rd.DebugOverlay.ClearBeforePass out.SetTextureDisplay(tex) out.Display() eps = 0.001 self.check_pixel_value(depth_tex, 160, 135, [0.9, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 160, 165, [0.0, 0.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 250, 150, [0.5, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 250, 250, [0.95, 0.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 50, 50, [1.0, 0.0/255.0, 0.0, 1.0], eps=eps) rdtest.log.success("Clear before pass colour and depth values as expected") # Check clear before draw tex.resourceId = col_tex tex.overlay = rd.DebugOverlay.ClearBeforeDraw out.SetTextureDisplay(tex) out.Display() eps = 1.0/256.0 # These are all pass triangles, should be cleared self.check_pixel_value(col_tex, 250, 250, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(col_tex, 125, 125, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(col_tex, 125, 175, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(col_tex, 50, 50, [0.0, 0.0, 0.0, 0.0], eps=eps) # These should be identical self.check_pixel_value(col_tex, 291, 150, [0.977, 0.977, 0.977, 1.0], eps=0.075) self.check_pixel_value(col_tex, 200, 51, [1.0, 0.5, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 65, [1.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 79, [0.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(col_tex, 200, 93, [0.0, 1.0, 0.0, 1.0], eps=eps) tex.resourceId = depth_tex tex.overlay = rd.DebugOverlay.ClearBeforeDraw out.SetTextureDisplay(tex) out.Display() eps = 0.001 # Without the pass, depth/stencil results are different self.check_pixel_value(depth_tex, 160, 135, [0.5, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 160, 165, [0.5, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 250, 150, [0.5, 85.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 250, 250, [1.0, 0.0/255.0, 0.0, 1.0], eps=eps) self.check_pixel_value(depth_tex, 50, 50, [1.0, 0.0/255.0, 0.0, 1.0], eps=eps) rdtest.log.success("Clear before draw colour and depth values as expected") rdtest.log.success("All overlays as expected for main draw") # Now test overlays on a render-to-slice/mip case for mip in [2, 3]: sub_marker: rd.DrawcallDescription = self.find_draw("Subresources mip {}".format(mip)) self.controller.SetFrameEvent(sub_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() col_tex = pipe.GetOutputTargets()[0].resourceId sub = rd.Subresource(pipe.GetOutputTargets()[0].firstMip, 0, 0) for overlay in rd.DebugOverlay: if overlay == rd.DebugOverlay.NoOverlay: continue # These overlays are just displaymodes really, not actually separate overlays if overlay == rd.DebugOverlay.NaN or overlay == rd.DebugOverlay.Clipping: continue if overlay == rd.DebugOverlay.ClearBeforeDraw or overlay == rd.DebugOverlay.ClearBeforePass: continue rdtest.log.success("Checking overlay {} with mip/slice rendering".format(str(overlay))) tex.resourceId = col_tex tex.overlay = overlay tex.subresource = sub out.SetTextureDisplay(tex) out.Display() overlay_id: rd.ResourceId = out.GetDebugOverlayTexID() shift = 0 if mip == 3: shift = 1 # All values in mip 0 should be 0 for all overlays self.check_pixel_value(overlay_id, 200, 150, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(0, 0, 0)) self.check_pixel_value(overlay_id, 197, 147, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(0, 0, 0)) self.check_pixel_value(overlay_id, 203, 153, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(0, 0, 0)) rdtest.log.success("Other mips are empty as expected for overlay {}".format(str(overlay))) if overlay == rd.DebugOverlay.Drawcall: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [0.8, 0.1, 0.8, 1.0], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 30 >> shift, 36 >> shift, [0.0, 0.0, 0.0, 0.5], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 70 >> shift, 34 >> shift, [0.8, 0.1, 0.8, 1.0], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 70 >> shift, 20 >> shift, [0.0, 0.0, 0.0, 0.5], sub=sub, eps=eps) elif overlay == rd.DebugOverlay.Wireframe: self.check_pixel_value(overlay_id, 36 >> shift, 36 >> shift, [200.0 / 255.0, 1.0, 0.0, 1.0], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 36 >> shift, 50 >> shift, [200.0 / 255.0, 1.0, 0.0, 1.0], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [200.0 / 255.0, 1.0, 0.0, 0.0], sub=sub, eps=eps) elif overlay == rd.DebugOverlay.Depth or overlay == rd.DebugOverlay.Stencil: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [0.0, 1.0, 0.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 30 >> shift, 36 >> shift, [0.0, 1.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 34 >> shift, [1.0, 0.0, 0.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 20 >> shift, [0.0, 1.0, 0.0, 0.0], sub=sub) elif overlay == rd.DebugOverlay.BackfaceCull: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [0.0, 1.0, 0.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 30 >> shift, 36 >> shift, [0.0, 1.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 34 >> shift, [1.0, 0.0, 0.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 20 >> shift, [0.0, 1.0, 0.0, 0.0], sub=sub) elif overlay == rd.DebugOverlay.ViewportScissor: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [0.2 * 0.7, 0.2 * 0.7, 0.9 * 0.7, 0.7 * 0.7], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 30 >> shift, 36 >> shift, [0.2 * 0.7, 0.2 * 0.7, 0.9 * 0.7, 0.7 * 0.7], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 70 >> shift, 34 >> shift, [0.2 * 0.7, 0.2 * 0.7, 0.9 * 0.7, 0.7 * 0.7], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 70 >> shift, 20 >> shift, [0.2 * 0.7, 0.2 * 0.7, 0.9 * 0.7, 0.7 * 0.7], sub=sub, eps=eps) if mip == 2: self.check_pixel_value(overlay_id, 6, 6, [0.1, 0.1, 0.1, 1.0], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 4, 4, [0.0, 0.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 0, 0, [1.0, 1.0, 1.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 20, 0, [0.0, 0.0, 0.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 40, 0, [1.0, 1.0, 1.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 60, 0, [0.0, 0.0, 0.0, 1.0], sub=sub) else: self.check_pixel_value(overlay_id, 4, 4, [0.1, 0.1, 0.1, 1.0], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 0, 0, [1.0, 1.0, 1.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 20, 0, [0.0, 0.0, 0.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 40, 0, [1.0, 1.0, 1.0, 1.0], sub=sub) elif overlay == rd.DebugOverlay.QuadOverdrawDraw or overlay == rd.DebugOverlay.QuadOverdrawPass: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [1.0, 1.0, 1.0, 1.0], sub=sub) self.check_pixel_value(overlay_id, 30 >> shift, 36 >> shift, [0.0, 0.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 20 >> shift, [0.0, 0.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 50 >> shift, 45 >> shift, [2.0, 2.0, 2.0, 2.0], sub=sub) elif overlay == rd.DebugOverlay.TriangleSizeDraw or overlay == rd.DebugOverlay.TriangleSizePass: if mip == 2: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [585.0, 585.0, 585.0, 1.0], sub=sub) else: self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [151.75, 151.75, 151.75, 1.0], sub=sub) self.check_pixel_value(overlay_id, 30 >> shift, 36 >> shift, [0.0, 0.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 34 >> shift, [0.0, 0.0, 0.0, 0.0], sub=sub) self.check_pixel_value(overlay_id, 70 >> shift, 20 >> shift, [0.0, 0.0, 0.0, 0.0], sub=sub) if mip == 2: self.check_pixel_value(overlay_id, 50 >> shift, 45 >> shift, [117.0, 117.0, 117.0, 1.0], sub=sub) else: self.check_pixel_value(overlay_id, 50 >> shift, 45 >> shift, [30.359375, 30.359375, 30.359375, 1.0], sub=sub) rdtest.log.success("Picked values are correct for mip {} overlay {}".format(sub.mip, str(overlay))) out.Shutdown()
def check_capture(self): id = self.get_last_draw().copyDestination tex_details = self.get_texture(id) self.controller.SetFrameEvent(self.get_last_draw().eventId, True) data = self.controller.GetTextureData(id, rd.Subresource(0, 0, 0)) first_pixel = struct.unpack_from("BBBB", data, 0) val = [255, 0, 255, 255] if not rdtest.value_compare(first_pixel, val): raise rdtest.TestFailureException( "First pixel should be clear color {}, not {}".format( val, first_pixel)) magic_pixel = struct.unpack_from("BBBB", data, (50 * tex_details.width + 320) * 4) # allow 127 or 128 for alpha val = [0, 0, 255, magic_pixel[3]] if not rdtest.value_compare(magic_pixel, val) or magic_pixel[3] not in [127, 128]: raise rdtest.TestFailureException( "Pixel @ 320,50 should be blue: {}, not {}".format( val, magic_pixel)) rdtest.log.success("Decoded pixels from texture data are correct") img_path = rdtest.get_tmp_path('preserved_alpha.png') self.controller.SetFrameEvent(self.get_last_draw().eventId, True) save_data = rd.TextureSave() save_data.resourceId = id save_data.destType = rd.FileType.PNG save_data.alpha = rd.AlphaMapping.Discard # this should not discard the alpha self.controller.SaveTexture(save_data, img_path) data = rdtest.png_load_data(img_path) magic_pixel = struct.unpack_from("BBBB", data[-1 - 50], 320 * 4) val = [0, 0, 255, magic_pixel[3]] if not rdtest.value_compare(magic_pixel, val) or magic_pixel[3] not in [127, 128]: raise rdtest.TestFailureException( "Pixel @ 320,50 should be blue: {}, not {}".format( val, magic_pixel)) draw = self.find_draw("Draw") self.controller.SetFrameEvent(draw.eventId, False) postvs_data = self.get_postvs(rd.MeshDataStage.VSOut, 0, draw.numIndices) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_Position': [-0.5, -0.5, 0.0, 1.0], 'v2fcol': [1.0, 0.0, 0.0, 1.0], }, 1: { 'vtx': 1, 'idx': 1, 'gl_Position': [0.0, 0.5, 0.0, 1.0], 'v2fcol': [0.0, 1.0, 0.0, 1.0], }, 2: { 'vtx': 2, 'idx': 2, 'gl_Position': [0.5, -0.5, 0.0, 1.0], 'v2fcol': [0.0, 0.0, 1.0, 1.0], }, } self.check_mesh_data(postvs_ref, postvs_data) results = self.controller.FetchCounters([ rd.GPUCounter.RasterizedPrimitives, rd.GPUCounter.VSInvocations, rd.GPUCounter.FSInvocations ]) results = [r for r in results if r.eventId == draw.eventId] if len(results) != 3: raise rdtest.TestFailureException( "Expected 3 results, got {} results".format(len(results))) for r in results: r: rd.CounterResult val = r.value.u32 if r.counter == rd.GPUCounter.RasterizedPrimitives: if not rdtest.value_compare(val, 1): raise rdtest.TestFailureException( "RasterizedPrimitives result {} is not as expected". format(val)) else: rdtest.log.success( "RasterizedPrimitives result is as expected") elif r.counter == rd.GPUCounter.VSInvocations: if not rdtest.value_compare(val, 3): raise rdtest.TestFailureException( "VSInvocations result {} is not as expected".format( val)) else: rdtest.log.success("VSInvocations result is as expected") elif r.counter == rd.GPUCounter.FSInvocations: if val < int(0.1 * tex_details.width * tex_details.height): raise rdtest.TestFailureException( "FSInvocations result {} is not as expected".format( val)) else: rdtest.log.success("FSInvocations result is as expected") else: raise rdtest.TestFailureException( "Unexpected counter result {}".format(r.counter)) rdtest.log.success("Counter data retrieved successfully")
def check_capture(self): actions = self.controller.GetRootActions() for d in self.controller.GetRootActions(): # Only process test actions if not d.customName.startswith('Test'): continue # Go to the last child action self.controller.SetFrameEvent(d.children[-1].eventId, True) if any(['UInt tex' in d.customName for d in d.children]): value_selector = lambda x: x.uintValue shader_out = (0, 1, 1234, 5) elif any(['SInt tex' in d.customName for d in d.children]): value_selector = lambda x: x.intValue shader_out = (0, 1, -1234, 5) else: value_selector = lambda x: x.floatValue shader_out = (0.0, 1.0, 0.1234, 0.5) pipe: rd.PipeState = self.controller.GetPipelineState() rt: rd.BoundResource = pipe.GetOutputTargets()[0] vp: rd.Viewport = pipe.GetViewport(0) tex = rt.resourceId x, y = (int(vp.width / 2), int(vp.height / 2)) tex_details = self.get_texture(tex) sub = rd.Subresource() if tex_details.arraysize > 1: sub.slice = rt.firstSlice if tex_details.mips > 1: sub.mip = rt.firstMip modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) # Should be at least two modifications in every test - clear and action self.check(len(modifs) >= 2) # Check that the modifications are self consistent - postmod of each should match premod of the next for i in range(len(modifs) - 1): if value_selector(modifs[i].postMod.col) != value_selector(modifs[i + 1].preMod.col): raise rdtest.TestFailureException( "postmod at {}: {} doesn't match premod at {}: {}".format(modifs[i].eventId, value_selector(modifs[i].postMod.col), modifs[i + 1].eventId, value_selector(modifs[i].preMod.col))) if self.get_action(modifs[i].eventId).flags & rd.ActionFlags.Drawcall: if not rdtest.value_compare(value_selector(modifs[i].shaderOut.col), shader_out): raise rdtest.TestFailureException( "Shader output {} isn't as expected {}".format(value_selector(modifs[i].shaderOut.col), shader_out)) rdtest.log.success("shader output and premod/postmod is consistent") # The current pixel value should match the last postMod self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) # Also the red channel should be zero, as it indicates errors self.check(float(value_selector(modifs[-1].postMod.col)[0]) == 0.0)
def pixel_debug(self, draw: rd.DrawcallDescription): pipe: rd.PipeState = self.controller.GetPipelineState() if pipe.GetShader(rd.ShaderStage.Pixel) == rd.ResourceId.Null(): rdtest.log.print("No pixel shader bound at {}: {}".format(draw.eventId, draw.name)) return if len(pipe.GetOutputTargets()) == 0 and pipe.GetDepthTarget().resourceId == rd.ResourceId.Null(): rdtest.log.print("No render targets bound at {}: {}".format(draw.eventId, draw.name)) return if not (draw.flags & rd.DrawFlags.Drawcall): rdtest.log.print("{}: {} is not a debuggable drawcall".format(draw.eventId, draw.name)) return viewport = pipe.GetViewport(0) # TODO, query for some pixel this drawcall actually touched. x = int(random.random()*viewport.width + viewport.x) y = int(random.random()*viewport.height + viewport.y) target = rd.ResourceId.Null() if len(pipe.GetOutputTargets()) > 0: valid_targets = [o.resourceId for o in pipe.GetOutputTargets() if o.resourceId != rd.ResourceId.Null()] rdtest.log.print("Valid targets at {} are {}".format(draw.eventId, valid_targets)) if len(valid_targets) > 0: target = valid_targets[int(random.random()*len(valid_targets))] if target == rd.ResourceId.Null(): target = pipe.GetDepthTarget().resourceId if target == rd.ResourceId.Null(): rdtest.log.print("No targets bound! Can't fetch history at {}".format(draw.eventId)) return rdtest.log.print("Fetching history for %d,%d on target %s" % (x, y, str(target))) history = self.controller.PixelHistory(target, x, y, rd.Subresource(0, 0, 0), rd.CompType.Typeless) rdtest.log.success("Pixel %d,%d has %d history events" % (x, y, len(history))) lastmod: rd.PixelModification = None for i in reversed(range(len(history))): mod = history[i] draw = self.find_draw('', mod.eventId) if draw is None or not (draw.flags & rd.DrawFlags.Drawcall): continue rdtest.log.print(" hit %d at %d is %s (%s)" % (i, mod.eventId, draw.name, str(draw.flags))) lastmod = history[i] rdtest.log.print("Got a hit on a drawcall at event %d" % lastmod.eventId) if mod.sampleMasked or mod.backfaceCulled or mod.depthClipped or mod.viewClipped or mod.scissorClipped or mod.shaderDiscarded or mod.depthTestFailed or mod.stencilTestFailed: rdtest.log.print("This hit failed, looking for one that passed....") lastmod = None continue if not mod.shaderOut.IsValid(): rdtest.log.print("This hit's shader out is not valid, looking for one that valid....") lastmod = None continue break if target == pipe.GetDepthTarget().resourceId: rdtest.log.print("Not doing pixel debug for depth output") return if lastmod is not None: rdtest.log.print("Debugging pixel {},{} @ {}, primitive {}".format(x, y, lastmod.eventId, lastmod.primitiveID)) self.controller.SetFrameEvent(lastmod.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() trace = self.controller.DebugPixel(x, y, 0, lastmod.primitiveID) if trace.debugger is None: self.controller.FreeTrace(trace) rdtest.log.print("No debug result") return cycles, variables = self.process_trace(trace) output_index = [o.resourceId for o in pipe.GetOutputTargets()].index(target) if draw.outputs[0] == rd.ResourceId.Null(): rdtest.log.success('Successfully debugged pixel in {} cycles, skipping result check due to no output'.format(cycles)) self.controller.FreeTrace(trace) elif (draw.flags & rd.DrawFlags.Instanced) and draw.numInstances > 1: rdtest.log.success('Successfully debugged pixel in {} cycles, skipping result check due to instancing'.format(cycles)) self.controller.FreeTrace(trace) elif pipe.GetColorBlends()[output_index].writeMask == 0: rdtest.log.success('Successfully debugged pixel in {} cycles, skipping result check due to write mask'.format(cycles)) self.controller.FreeTrace(trace) else: rdtest.log.print("At event {} the target is index {}".format(lastmod.eventId, output_index)) output_sourcevar = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, output_index) if output_sourcevar is not None: debugged = self.evaluate_source_var(output_sourcevar, variables) self.controller.FreeTrace(trace) debuggedValue = list(debugged.value.f32v[0:4]) # For now, ignore debugged values that are uninitialised. This is an application bug but it causes # false reports of problems for idx in range(4): if debugged.value.u32v[idx] == 0xcccccccc: debuggedValue[idx] = lastmod.shaderOut.col.floatValue[idx] # Unfortunately we can't ever trust that we should get back a matching results, because some shaders # rely on undefined/inaccurate maths that we don't emulate. # So the best we can do is log an error for manual verification is_eq, diff_amt = rdtest.value_compare_diff(lastmod.shaderOut.col.floatValue, debuggedValue, eps=5.0E-06) if not is_eq: rdtest.log.error( "Debugged value {} at EID {} {},{}: {} difference. {} doesn't exactly match history shader output {}".format( debugged.name, lastmod.eventId, x, y, diff_amt, debuggedValue, lastmod.shaderOut.col.floatValue)) rdtest.log.success('Successfully debugged pixel in {} cycles, result matches'.format(cycles)) else: # This could be an application error - undefined but seen in the wild rdtest.log.error("At EID {} No output variable declared for index {}".format(lastmod.eventId, output_index)) self.controller.SetFrameEvent(draw.eventId, True)
def check_test(self, fmt_name: str, name: str, test_mode: int): pipe: rd.PipeState = self.controller.GetPipelineState() image_view = (test_mode != Texture_Zoo.TEST_CAPTURE) if image_view: bound_res: rd.BoundResource = pipe.GetOutputTargets()[0] else: bound_res: rd.BoundResource = pipe.GetReadOnlyResources( rd.ShaderStage.Pixel)[0].resources[0] texs = self.controller.GetTextures() for t in texs: self.textures[t.resourceId] = t tex_id: rd.ResourceId = bound_res.resourceId tex: rd.TextureDescription = self.textures[tex_id] comp_type: rd.CompType = tex.format.compType if bound_res.typeCast != rd.CompType.Typeless: comp_type = bound_res.typeCast # When not running proxied, save non-typecasted textures to disk if not image_view and not self.proxied and ( tex.format.compType == comp_type or tex.format.type == rd.ResourceFormatType.D24S8 or tex.format.type == rd.ResourceFormatType.D32S8): save_data = rd.TextureSave() save_data.resourceId = tex_id save_data.destType = rd.FileType.DDS save_data.sample.mapToArray = True self.textures[self.filename] = tex path = rdtest.get_tmp_path(self.filename + '.dds') success: bool = self.controller.SaveTexture(save_data, path) if not success: if self.d3d_mode: raise rdtest.TestFailureException( "Couldn't save DDS to {} on D3D.".format( self.filename)) try: os.remove(path) except Exception: pass save_data.destType = rd.FileType.PNG save_data.slice.sliceIndex = 0 save_data.sample.sampleIndex = 0 path = path.replace('.dds', '.png') if comp_type == rd.CompType.UInt: save_data.comp.blackPoint = 0.0 save_data.comp.whitePoint = 255.0 elif comp_type == rd.CompType.SInt: save_data.comp.blackPoint = -255.0 save_data.comp.whitePoint = 0.0 elif comp_type == rd.CompType.SNorm: save_data.comp.blackPoint = -1.0 save_data.comp.whitePoint = 0.0 success: bool = self.controller.SaveTexture(save_data, path) if not success: try: os.remove(path) except Exception: pass value0 = [] comp_count = tex.format.compCount # When viewing PNGs only compare the components that the original texture had if test_mode == Texture_Zoo.TEST_PNG: comp_count = self.textures[self.filename] tex.msSamp = 0 tex.arraysize = 1 tex.depth = 1 self.fake_msaa = 'MSAA' in name elif test_mode == Texture_Zoo.TEST_DDS: tex.arraysize = self.textures[self.filename].arraysize tex.msSamp = self.textures[self.filename].msSamp self.fake_msaa = 'MSAA' in name # HACK: We don't properly support BGRX, so just drop the alpha channel. We can't set this to compCount = 3 # internally because that's a 24-bit format with no padding... if 'B8G8R8X8' in fmt_name: comp_count = 3 # Completely ignore the alpha for BC1, our encoder doesn't pay attention to it if tex.format.type == rd.ResourceFormatType.BC1: comp_count = 3 # Calculate format-appropriate epsilon eps_significand = 1.0 # Account for the sRGB curve by more generous epsilon if comp_type == rd.CompType.UNormSRGB: eps_significand = 2.5 # Similarly SNorm essentially loses a bit of accuracy due to us only using negative values elif comp_type == rd.CompType.SNorm: eps_significand = 2.0 if tex.format.type == rd.ResourceFormatType.R4G4B4A4 or tex.format.type == rd.ResourceFormatType.R4G4: eps = (eps_significand / 15.0) elif rd.ResourceFormatType.BC1 <= tex.format.type <= rd.ResourceFormatType.BC3: eps = (eps_significand / 15.0) # 4-bit precision in some channels elif tex.format.type == rd.ResourceFormatType.R5G5B5A1 or tex.format.type == rd.ResourceFormatType.R5G6B5: eps = (eps_significand / 31.0) elif tex.format.type == rd.ResourceFormatType.R11G11B10: eps = (eps_significand / 31.0) # 5-bit mantissa in blue elif tex.format.type == rd.ResourceFormatType.R9G9B9E5: eps = ( eps_significand / 63.0 ) # we have 9 bits of data, but might lose 2-3 due to shared exponent elif tex.format.type == rd.ResourceFormatType.BC6 and tex.format.compType == rd.CompType.SNorm: eps = (eps_significand / 63.0 ) # Lose a bit worth of precision for the signed version elif rd.ResourceFormatType.BC4 <= tex.format.type <= rd.ResourceFormatType.BC7: eps = (eps_significand / 127.0) elif tex.format.compByteWidth == 1: eps = (eps_significand / 255.0) elif comp_type == rd.CompType.Depth and tex.format.compCount == 2: eps = (eps_significand / 255.0) # stencil is only 8-bit elif tex.format.type == rd.ResourceFormatType.A8: eps = (eps_significand / 255.0) elif tex.format.type == rd.ResourceFormatType.R10G10B10A2: eps = (eps_significand / 1023.0) else: # half-floats have 11-bit mantissa. This epsilon is tight enough that we can be sure # any remaining errors are implementation inaccuracy and not our bug eps = (eps_significand / 2047.0) for mp in range(tex.mips): for sl in range(max(tex.arraysize, max(1, tex.depth >> mp))): z = 0 if tex.depth > 1: z = sl for sm in range(tex.msSamp): cur_sub = self.sub(mp, sl, sm) tex_display = rd.TextureDisplay() tex_display.resourceId = tex_id tex_display.subresource = cur_sub tex_display.flipY = self.opengl_mode tex_display.typeCast = comp_type tex_display.alpha = False tex_display.scale = 1.0 / float(1 << mp) tex_display.backgroundColor = rd.FloatVector( 0.0, 0.0, 0.0, 1.0) if comp_type == rd.CompType.UInt: tex_display.rangeMin = 0.0 tex_display.rangeMax = 255.0 elif comp_type == rd.CompType.SInt: tex_display.rangeMin = -255.0 tex_display.rangeMax = 0.0 elif comp_type == rd.CompType.SNorm: tex_display.rangeMin = -1.0 tex_display.rangeMax = 0.0 self.out.SetTextureDisplay(tex_display) self.out.Display() pixels: bytes = self.out.ReadbackOutputTexture() dim = self.out.GetDimensions() stencilpixels = None # Grab stencil separately if tex.format.type == rd.ResourceFormatType.D16S8 or tex.format.type == rd.ResourceFormatType.D24S8 or tex.format.type == rd.ResourceFormatType.D32S8: tex_display.red = False tex_display.green = True tex_display.blue = False tex_display.alpha = False self.out.SetTextureDisplay(tex_display) self.out.Display() stencilpixels: bytes = self.out.ReadbackOutputTexture() # Grab alpha if needed (since the readback output is RGB only) if comp_count == 4 or tex.format.type == rd.ResourceFormatType.A8: tex_display.red = False tex_display.green = False tex_display.blue = False tex_display.alpha = True self.out.SetTextureDisplay(tex_display) self.out.Display() alphapixels: bytes = self.out.ReadbackOutputTexture() all_good = True for x in range(max(1, tex.width >> mp)): if not all_good: break for y in range(max(1, tex.height >> mp)): expected = self.get_expected_value( comp_count, comp_type, cur_sub, test_mode, tex, x, y, z) # for this test to work the expected values have to be within the range we selected for # display above for i in expected: if i < tex_display.rangeMin or tex_display.rangeMax < i: raise rdtest.TestFailureException( "expected value {} is outside of texture display range! {} - {}" .format(i, tex_display.rangeMin, tex_display.rangeMax)) # convert the expected values to range-adapted values for i in range(len(expected)): expected[i] = (expected[i] - tex_display.rangeMin) / ( tex_display.rangeMax - tex_display.rangeMin) # get the bytes from the displayed pixel offs = y * dim[0] * 3 + x * 3 displayed = [ int(a) for a in pixels[offs:offs + comp_count] ] if comp_count == 4: del displayed[3] displayed.append(int(alphapixels[offs])) if stencilpixels is not None: del displayed[1:] displayed.append(int(stencilpixels[offs])) if tex.format.type == rd.ResourceFormatType.A8: displayed = [int(alphapixels[offs])] # normalise the displayed values for i in range(len(displayed)): displayed[i] = float(displayed[i]) / 255.0 # For SRGB textures match linear picked values. We do this for alpha too since it's rendered # via RGB if comp_type == rd.CompType.UNormSRGB: displayed[0:4] = [ srgb2linear(x) for x in displayed[0:4] ] # alpha channel in 10:10:10:2 has extremely low precision, and the ULP requirements mean # we basically can't trust anything between 0 and 1 on float formats. Just round in that # case as it still lets us differentiate between alpha 0.0-0.5 and 0.5-1.0 if tex.format.type == rd.ResourceFormatType.R10G10B10A2 and comp_type != rd.CompType.UInt: displayed[3] = round(displayed[3]) * 1.0 # Handle 1-bit alpha if tex.format.type == rd.ResourceFormatType.R5G5B5A1: displayed[ 3] = 1.0 if displayed[3] >= 0.5 else 0.0 # Need an additional 1/255 epsilon to account for us going via a 8-bit backbuffer for display if not rdtest.value_compare( displayed, expected, 1.0 / 255.0 + eps): #rdtest.log.print( # "Quick-checking ({},{}) of slice {}, mip {}, sample {} of {} {} got {}. Expected {}.".format( # x, y, sl, mp, sm, name, fmt_name, displayed, expected) + # "Falling back to pixel picking tests.") # Currently this seems to fail in some proxy scenarios with sRGB, but since it's not a # real error we just silently swallow it all_good = False break if all_good: continue for x in range(max(1, tex.width >> mp)): for y in range(max(1, tex.height >> mp)): expected = self.get_expected_value( comp_count, comp_type, cur_sub, test_mode, tex, x, y, z) picked = self.get_picked_pixel_value( comp_count, comp_type, cur_sub, tex, tex_id, x, y) if mp == 0 and sl == 0 and sm == 0 and x == 0 and y == 0: value0 = picked if not rdtest.value_compare(picked, expected, eps): raise rdtest.TestFailureException( "At ({},{}) of slice {}, mip {}, sample {} of {} {} got {}. Expected {}" .format(x, y, sl, mp, sm, name, fmt_name, picked, expected)) if not image_view: output_tex = pipe.GetOutputTargets()[0].resourceId # in the test captures pick the output texture, it should be identical to the # (0,0) pixel in slice 0, mip 0, sample 0 view: rd.Viewport = pipe.GetViewport(0) val: rd.PixelValue = self.pick( pipe.GetOutputTargets()[0].resourceId, int(view.x + view.width / 2), int(view.y + view.height / 2), rd.Subresource(), rd.CompType.Typeless) picked = list(val.floatValue) # A8 picked values come out in alpha, but we want to compare against the single channel if tex.format.type == rd.ResourceFormatType.A8: picked[0] = picked[3] # Clamp to number of components in the texture picked = picked[0:comp_count] # If we didn't get a value0 (because we did all texture render compares) then fetch it here if len(value0) == 0: value0 = self.get_picked_pixel_value(comp_count, comp_type, rd.Subresource(), tex, tex_id, 0, 0) # Up-convert any non-float expected values to floats value0 = [float(x) for x in value0] # For depth/stencil images, one of either depth or stencil should match if comp_type == rd.CompType.Depth and len(value0) == 2: if picked[0] == 0.0: value0[0] = 0.0 # normalise stencil value if it isn't already if picked[1] > 1.0: picked[1] /= 255.0 elif picked[0] > 1.0: # un-normalised stencil being rendered in red, match against our stencil expectation picked[0] /= 255.0 value0[0] = value0[1] value0[1] = 0.0 else: if picked[1] == 0.0: value0[1] = 0.0 if picked[1] > 1.0: picked[1] /= 255.0 if not rdtest.value_compare(picked, value0, eps): raise rdtest.TestFailureException( "In {} {} Top-left pixel as rendered is {}. Expected {}". format(name, fmt_name, picked, value0))
def primary_test(self): test_marker: rd.ActionDescription = self.find_action("Test Begin") self.controller.SetFrameEvent(test_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() rt: rd.BoundResource = pipe.GetOutputTargets()[0] tex = rt.resourceId tex_details = self.get_texture(tex) sub = rd.Subresource() if tex_details.arraysize > 1: sub.slice = rt.firstSlice if tex_details.mips > 1: sub.mip = rt.firstMip begin_renderpass_eid = self.find_action( "Begin RenderPass").next.eventId depth_write_eid = self.find_action("Depth Write").next.eventId stencil_write_eid = self.find_action("Stencil Write").next.eventId unbound_fs_eid = self.find_action( "Unbound Fragment Shader").next.eventId background_eid = self.find_action("Background").next.eventId cull_eid = self.find_action("Cull Front").next.eventId test_eid = self.find_action("Test Begin").next.eventId fixed_scissor_fail_eid = self.find_action( "Fixed Scissor Fail").next.eventId fixed_scissor_pass_eid = self.find_action( "Fixed Scissor Pass").next.eventId dynamic_stencil_ref_eid = self.find_action( "Dynamic Stencil Ref").next.eventId dynamic_stencil_mask_eid = self.find_action( "Dynamic Stencil Mask").next.eventId depth_test_eid = self.find_action("Depth Test").next.eventId depth_bounds_prep_eid = self.find_action( "Depth Bounds Prep").next.eventId depth_bounds_clip_eid = self.find_action( "Depth Bounds Clip").next.eventId # For pixel 190, 149 inside the red triangle x, y = 190, 149 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, unbound_fs_eid], [passed, True], [unboundPS, True], [primitive_id, 0]], [[event_id, stencil_write_eid], [passed, True]], [[event_id, background_eid], [depth_test_failed, True], [post_mod_col, (1.0, 0.0, 0.0, 1.0)]], [[event_id, test_eid], [stencil_test_failed, True]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 190, 150 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, depth_write_eid], [passed, True]], [[event_id, background_eid], [depth_test_failed, True]], [[event_id, cull_eid], [culled, True]], [[event_id, test_eid], [depth_test_failed, True]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 200, 50 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, background_eid], [passed, True]], [[event_id, test_eid], [passed, True], [primitive_id, 7]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 150, 250 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, background_eid], [shader_discarded, True]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 330, 145 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, test_eid], [passed, True], [primitive_id, 3], [shader_out_col, (0.0, 0.0, 0.0, 2.75)]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 340, 145 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, test_eid], [passed, False], [depth_clipped, True]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 330, 105 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, depth_bounds_prep_eid], [passed, True], [primitive_id, 0], [shader_out_col, (1.0, 0.0, 0.0, 2.75)]], [[event_id, depth_bounds_clip_eid], [passed, True], [primitive_id, 0], [shader_out_col, (0.0, 1.0, 0.0, 2.75)]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 320, 105 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, depth_bounds_prep_eid], [passed, True], [primitive_id, 0], [shader_out_col, (1.0, 0.0, 0.0, 2.75)]], [[event_id, depth_bounds_clip_eid], [passed, False], [depth_bounds_failed, True]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) x, y = 345, 105 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, depth_bounds_prep_eid], [passed, True], [primitive_id, 0], [shader_out_col, (1.0, 0.0, 0.0, 2.75)]], [[event_id, depth_bounds_clip_eid], [passed, False], [depth_bounds_failed, True]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) rdtest.log.print("Testing dynamic state pipelines") self.controller.SetFrameEvent(dynamic_stencil_mask_eid, True) x, y = 100, 250 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, background_eid], [passed, True]], [[event_id, fixed_scissor_fail_eid], [scissor_clipped, True]], [[event_id, fixed_scissor_pass_eid], [passed, True], [shader_out_col, (0.0, 1.0, 0.0, 2.75)], [post_mod_col, (0.0, 1.0, 0.0, 1.0)]], [[event_id, dynamic_stencil_ref_eid], [passed, True], [shader_out_col, (0.0, 0.0, 1.0, 2.75)], [post_mod_col, (0.0, 0.0, 1.0, 1.0)]], [[event_id, dynamic_stencil_mask_eid], [passed, True], [shader_out_col, (0.0, 1.0, 1.0, 2.75)], [post_mod_col, (0.0, 1.0, 1.0, 1.0)]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast) rdtest.log.print("Testing depth test for per fragment reporting") self.controller.SetFrameEvent(depth_test_eid, True) x, y = 275, 260 rdtest.log.print("Testing pixel {}, {}".format(x, y)) modifs: List[rd.PixelModification] = self.controller.PixelHistory( tex, x, y, sub, rt.typeCast) events = [ [[event_id, begin_renderpass_eid], [passed, True]], [[event_id, background_eid], [passed, True]], [[event_id, depth_test_eid], [primitive_id, 0], [depth_test_failed, True], [shader_out_col, (1.0, 1.0, 1.0, 2.75)], [shader_out_depth, 0.97], [post_mod_col, (1.0, 0.0, 1.0, 1.0)], [post_mod_depth, 0.95]], [[event_id, depth_test_eid], [primitive_id, 1], [depth_test_failed, False], [shader_out_col, (1.0, 1.0, 0.0, 2.75)], [shader_out_depth, 0.20], [post_mod_col, (1.0, 1.0, 0.0, 1.0)], [post_mod_depth, 0.20]], [[event_id, depth_test_eid], [primitive_id, 2], [depth_test_failed, True], [shader_out_col, (1.0, 0.0, 0.0, 2.75)], [shader_out_depth, 0.30], [post_mod_col, (1.0, 1.0, 0.0, 1.0)], [post_mod_depth, 0.20]], [[event_id, depth_test_eid], [primitive_id, 3], [depth_test_failed, False], [shader_out_col, (0.0, 0.0, 1.0, 2.75)], [shader_out_depth, 0.10], [post_mod_col, (0.0, 0.0, 1.0, 1.0)], [post_mod_depth, 0.10]], [[event_id, depth_test_eid], [primitive_id, 4], [depth_test_failed, False], [shader_out_col, (1.0, 1.0, 1.0, 2.75)], [shader_out_depth, 0.05], [post_mod_col, (0.0, 0.0, 1.0, 1.0)], [post_mod_depth, 0.10]], ] self.check_events(events, modifs, False) self.check_pixel_value(tex, x, y, value_selector(modifs[-1].postMod.col), sub=sub, cast=rt.typeCast)
def sub(self, mip: int, slice: int, sample: int): if self.fake_msaa: return rd.Subresource(mip, slice * 2 + sample, 0) else: return rd.Subresource(mip, slice, sample)
def check_capture(self): first_action = self.get_first_action() found = False sdfile = self.controller.GetStructuredFile() for e in first_action.events: c: rd.SDChunk = sdfile.chunks[e.chunkIndex] if 'Unmap' in c.name: found = True if not found: raise rdtest.TestFailureException( "Expected an Unmap() chunk in frame 0, but couldn't find it!") last_action: rd.ActionDescription = self.get_last_action() self.controller.SetFrameEvent(last_action.eventId, True) self.check_triangle(out=last_action.copyDestination) self.check_export(self.capture_filename) action = self.find_action("Draw") self.controller.SetFrameEvent(action.eventId, False) postvs_data = self.get_postvs(action, rd.MeshDataStage.VSOut, 0, action.numIndices) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'SV_POSITION': [-0.5, -0.5, 0.0, 1.0], 'COLOR': [0.0, 1.0, 0.0, 1.0], 'TEXCOORD': [0.0, 0.0], }, 1: { 'vtx': 1, 'idx': 1, 'SV_POSITION': [0.0, 0.5, 0.0, 1.0], 'COLOR': [0.0, 1.0, 0.0, 1.0], 'TEXCOORD': [0.0, 1.0], }, 2: { 'vtx': 2, 'idx': 2, 'SV_POSITION': [0.5, -0.5, 0.0, 1.0], 'COLOR': [0.0, 1.0, 0.0, 1.0], 'TEXCOORD': [1.0, 0.0], }, } self.check_mesh_data(postvs_ref, postvs_data) # Check that nothing breaks if we call typical enumeration functions on resources for res in self.controller.GetResources(): res: rd.ResourceDescription self.controller.GetShaderEntryPoints(res.resourceId) self.controller.GetUsage(res.resourceId) self.controller.GetBufferData(res.resourceId, 0, 0) self.controller.GetTextureData(res.resourceId, rd.Subresource())
def check_capture(self): draw = self.find_draw("Draw") self.controller.SetFrameEvent(draw.eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() bind: rd.ShaderBindpointMapping = pipe.GetBindpointMapping( rd.ShaderStage.Fragment) texs: List[rd.BoundResourceArray] = pipe.GetReadOnlyResources( rd.ShaderStage.Fragment) if len(bind.readOnlyResources) != 2: raise rdtest.TestFailureException( "Expected 2 textures bound, not {}".format( len(bind.readOnlyResources))) if bind.readOnlyResources[0].bind != 2: raise rdtest.TestFailureException( "First texture should be on slot 2, not {}".format( bind.readOnlyResources[0].bind)) id = texs[2].resources[0].resourceId tex_details = self.get_texture(id) res_details = self.get_resource(id) if res_details.name != "Red 2D": raise rdtest.TestFailureException( "First texture should be Red 2D texture, not {}".format( res_details.name)) if tex_details.dimension != 2: raise rdtest.TestFailureException( "First texture should be 2D texture, not {}".format( tex_details.dimension)) if tex_details.width != 8 or tex_details.height != 8: raise rdtest.TestFailureException( "First texture should be 8x8, not {}x{}".format( tex_details.width, tex_details.height)) data = self.controller.GetTextureData(id, rd.Subresource(0, 0, 0)) first_pixel = struct.unpack_from("BBBB", data, 0) if not rdtest.value_compare(first_pixel, (255, 0, 0, 255)): raise rdtest.TestFailureException( "Texture should contain red, not {}".format(first_pixel)) rdtest.log.success("First texture is as expected") if bind.readOnlyResources[1].bind != 3: raise rdtest.TestFailureException( "First texture should be on slot 3, not {}".format( texs[0].bindPoint.bind)) id = texs[3].resources[0].resourceId tex_details = self.get_texture(id) res_details = self.get_resource(id) if res_details.name != "Green 3D": raise rdtest.TestFailureException( "First texture should be Green 3D texture, not {}".format( res_details.name)) if tex_details.dimension != 3: raise rdtest.TestFailureException( "First texture should be 3D texture, not {}".format( tex_details.dimension)) if tex_details.width != 4 or tex_details.height != 4 or tex_details.depth != 4: raise rdtest.TestFailureException( "First texture should be 4x4x4, not {}x{}x{}".format( tex_details.width, tex_details.height, tex_details.depth)) data = self.controller.GetTextureData(id, rd.Subresource(0, 0, 0)) first_pixel = struct.unpack_from("BBBB", data, 0) if not rdtest.value_compare(first_pixel, (0, 255, 0, 255)): raise rdtest.TestFailureException( "Texture should contain green, not {}".format(first_pixel)) rdtest.log.success("Second texture is as expected") self.check_pixel_value(pipe.GetOutputTargets()[0].resourceId, 0.5, 0.5, [1.0, 1.0, 0.0, 0.2]) rdtest.log.success("Picked value is as expected")