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 dumpImage(controller, eventId, outputDir, tracefile): draw = findDrawWithEventId(controller, eventId) if draw is None: raise RuntimeError("Couldn't find draw call with eventId " + str(eventId)) controller.SetFrameEvent(draw.eventId, True) texsave = rd.TextureSave() # Select the first color output texsave.resourceId = draw.outputs[0] if texsave.resourceId == rd.ResourceId.Null(): return filepath = Path(outputDir) filepath.mkdir(parents=True, exist_ok=True) filepath = filepath / (tracefile + "-" + str(int(draw.eventId)) + ".png") print("Saving image at eventId %d: %s to %s" % (draw.eventId, draw.name, filepath)) # Most formats can only display a single image per file, so we select the # first mip and first slice texsave.mip = 0 texsave.slice.sliceIndex = 0 # For formats with an alpha channel, preserve it texsave.alpha = rd.AlphaMapping.Preserve texsave.destType = rd.FileType.PNG controller.SaveTexture(texsave, str(filepath))
def check_clearbeforedraw_depth(self, out, depthId): # Test ClearBeforeDraw with a depth target tex = rd.TextureDisplay() tex.overlay = rd.DebugOverlay.ClearBeforeDraw tex.resourceId = depthId out.SetTextureDisplay(tex) out.GetDebugOverlayTexID() # Called to refresh the overlay overlay = rd.DebugOverlay.ClearBeforeDraw test_name = str(overlay) + '.Depth' overlay_path = rdtest.get_tmp_path(test_name + '.png') ref_path = self.get_ref_path(test_name + '.png') save_data = rd.TextureSave() save_data.resourceId = depthId save_data.destType = rd.FileType.PNG save_data.channelExtract = 0 tolerance = 2 self.controller.SaveTexture(save_data, overlay_path) if not rdtest.png_compare(overlay_path, ref_path, tolerance): raise rdtest.TestFailureException("Reference and output image differ for overlay {}".format(test_name), overlay_path, ref_path) rdtest.log.success("Reference and output image are identical for {}".format(test_name))
def check_capture(self): tex = rd.TextureDisplay() # At each action, the centre pixel of the viewport should be green action = self.get_first_action() while action is not None: self.controller.SetFrameEvent(action.eventId, False) if action.flags & rd.ActionFlags.Drawcall: pipe = self.controller.GetPipelineState() tex = self.controller.GetPipelineState().GetOutputTargets()[0].resourceId view: rd.Viewport = self.controller.GetPipelineState().GetViewport(0) x,y = int(view.x + view.width / 2), int(view.y + view.height / 2) # convert to top-left co-ordinates for use with PickPixel y = self.get_texture(tex).height - y self.check_pixel_value(tex, x, y, [0.0, 1.0, 0.0, 1.0]) action = action.next rdtest.log.success("Draws are all green") # Now save the backbuffer to disk ref_path = rdtest.get_tmp_path('backbuffer.png') save_data = rd.TextureSave() save_data.resourceId = tex save_data.destType = rd.FileType.PNG self.controller.SaveTexture(save_data, ref_path) # Open the capture and grab the thumbnail, check that it is all green too (dirty way of verifying we didn't # break in-app updates but somehow end up with the right data) cap = rd.OpenCaptureFile() # Open a particular file status = cap.OpenFile(self.capture_filename, '', None) # Make sure the file opened successfully if status != rd.ReplayStatus.Succeeded: cap.Shutdown() raise rdtest.TestFailureException("Couldn't open '{}': {}".format(self.capture_filename, str(status))) thumb: rd.Thumbnail = cap.GetThumbnail(rd.FileType.PNG, 0) tmp_path = rdtest.get_tmp_path('thumbnail.png') with open(tmp_path, 'wb') as f: f.write(thumb.data) # The original thumbnail should also be identical, since we have the uncompressed extended thumbnail. if not rdtest.png_compare(tmp_path, ref_path): raise rdtest.TestFailureException("Reference backbuffer and thumbnail image differ", tmp_path, ref_path) rdtest.log.success("Thumbnail is identical to reference")
def saveTexture(self, ResourceId, saveFile): if not self.isFileOpened(): print('open log file first.') return False if ResourceId is None: return False saveData = rd.TextureSave() saveData.resourceId = ResourceId # saveData.comp = rd.CompType.UNorm saveData.typeHint = rd.CompType.UNorm saveData.channelExtract = -1 saveData.comp.blackPoint = 0.0 saveData.comp.whitePoint = 1.0 saveData.alpha = rd.AlphaMapping.Discard fileExt = saveFile.split('.')[-1] if fileExt == 'dds' or fileExt == 'DDS': saveData.destType = rd.FileType.DDS elif fileExt == 'png' or fileExt == 'PNG': # saveData.alpha = rd.AlphaMapping.Preserve saveData.destType = rd.FileType.PNG elif fileExt == 'jpg' or fileExt == 'JPG': saveData.jpegQuality = 100 saveData.destType = rd.FileType.JPG elif fileExt == 'bmp' or fileExt == 'BMP': saveData.destType = rd.FileType.BMP elif fileExt == 'tga' or fileExt == 'TGA': saveData.destType = rd.FileType.TGA elif fileExt == 'hdr' or fileExt == 'HDR': saveData.destType = rd.FileType.HDR elif fileExt == 'exr' or fileExt == 'EXR': saveData.typeHint = rd.CompType.Depth saveData.destType = rd.FileType.EXR elif fileExt == 'raw' or fileExt == 'RAW': saveData.destType = rd.FileType.RAW else: print('Cannot handle %s file' % fileExt) return False self.controller.SaveTexture(saveData, saveFile) return True
def sampleCode(controller): # Find the biggest drawcall in the whole capture draw = None for d in controller.GetDrawcalls(): draw = biggestDraw(draw, d) # Move to that draw controller.SetFrameEvent(draw.eventId, True) texsave = rd.TextureSave() # Select the first color output texsave.resourceId = draw.outputs[0] if texsave.resourceId == rd.ResourceId.Null(): return filename = str(int(texsave.resourceId)) print("Saving images of %s at %d: %s" % (filename, draw.eventId, draw.name)) # Save different types of texture # Blend alpha to a checkerboard pattern for formats without alpha support texsave.alpha = rd.AlphaMapping.BlendToCheckerboard # Most formats can only display a single image per file, so we select the # first mip and first slice texsave.mip = 0 texsave.slice.sliceIndex = 0 texsave.destType = rd.FileType.JPG controller.SaveTexture(texsave, filename + ".jpg") texsave.destType = rd.FileType.HDR controller.SaveTexture(texsave, filename + ".hdr") # For formats with an alpha channel, preserve it texsave.alpha = rd.AlphaMapping.Preserve texsave.destType = rd.FileType.PNG controller.SaveTexture(texsave, filename + ".png") # DDS textures can save multiple mips and array slices, so instead # of the default behaviour of saving mip 0 and slice 0, we set -1 # which saves *all* mips and slices texsave.mip = -1 texsave.slice.sliceIndex = -1 texsave.destType = rd.FileType.DDS controller.SaveTexture(texsave, filename + ".dds")
def image_save(self, draw: rd.DrawcallDescription): pipe: rd.PipeState = self.controller.GetPipelineState() texsave = rd.TextureSave() for res in pipe.GetOutputTargets(): texsave.resourceId = res.resourceId texsave.mip = res.firstMip self.save_texture(texsave) depth = pipe.GetDepthTarget() texsave.resourceId = depth.resourceId texsave.mip = depth.firstMip self.save_texture(texsave) rdtest.log.success('Successfully saved images at {}: {}'.format(draw.eventId, draw.name))
def extractTexture(self, drawcallId, state): """Save the texture in a png file (A bit dirty)""" bindpoints = state.GetBindpointMapping(rd.ShaderStage.Fragment) if not bindpoints.samplers: print(f"Warning: No texture found for drawcall {drawcallId}") return texture_bind = bindpoints.samplers[-1].bind resources = state.GetReadOnlyResources(rd.ShaderStage.Fragment) rid = resources[texture_bind].resources[0].resourceId texsave = rd.TextureSave() texsave.resourceId = rid texsave.mip = 0 texsave.slice.sliceIndex = 0 texsave.alpha = rd.AlphaMapping.Preserve texsave.destType = rd.FileType.PNG controller.SaveTexture(texsave, "{}{:05d}-texture.png".format(FILEPREFIX, drawcallId))
def check_final_backbuffer(self): img_path = util.get_tmp_path('backbuffer.png') ref_path = self.get_ref_path('backbuffer.png') last_draw: rd.DrawcallDescription = self.get_last_draw() self.controller.SetFrameEvent(last_draw.eventId, True) save_data = rd.TextureSave() save_data.resourceId = last_draw.copyDestination save_data.destType = rd.FileType.PNG self.controller.SaveTexture(save_data, img_path) if not util.png_compare(img_path, ref_path): raise TestFailureException("Reference and output backbuffer image differ", ref_path, img_path) log.success("Backbuffer is identical to reference")
def dump_resource(controller, draw): controller.SetFrameEvent(draw.eventId, True) texsave = rd.TextureSave() if DUMP_RENDER_TARGET: # dump render targtes (aka outputs) for idx, output in enumerate(draw.outputs): texsave.resourceId = output if texsave.resourceId != rd.ResourceId.Null(): filename = "draw_%04d_c%d" % (draw.eventId, idx) print("Saving images of %s at %d: %s" % (filename, draw.eventId, draw.name)) texsave.alpha = rd.AlphaMapping.BlendToCheckerboard texsave.mip = 0 texsave.slice.sliceIndex = 0 # For formats with an alpha channel, preserve it texsave.alpha = rd.AlphaMapping.Preserve texsave.destType = rd.FileType.PNG controller.SaveTexture(texsave, filename + ".png")
def check_capture(self): self.check_final_backbuffer() out: rd.ReplayOutput = self.controller.CreateOutput(rd.CreateHeadlessWindowingData(), rd.ReplayOutputType.Texture) self.check(out is not None) out.SetDimensions(100, 100) test_marker: rd.DrawcallDescription = self.find_draw("Test") self.controller.SetFrameEvent(test_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() tex = rd.TextureDisplay() tex.resourceId = pipe.GetOutputTargets()[0].resourceId 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 # Unfortunately line-fill rendering seems to vary too much by IHV, so gives inconsistent results if overlay == rd.DebugOverlay.Wireframe: continue tex.overlay = overlay out.SetTextureDisplay(tex) overlay_path = rdtest.get_tmp_path(str(overlay) + '.png') ref_path = self.get_ref_path(str(overlay) + '.png') save_data = rd.TextureSave() save_data.resourceId = out.GetDebugOverlayTexID() save_data.destType = rd.FileType.PNG save_data.comp.blackPoint = 0.0 save_data.comp.whitePoint = 1.0 tolerance = 2 # These overlays return grayscale above 1, so rescale to an expected range. if (overlay == rd.DebugOverlay.QuadOverdrawDraw or overlay == rd.DebugOverlay.QuadOverdrawPass or overlay == rd.DebugOverlay.TriangleSizeDraw or overlay == rd.DebugOverlay.TriangleSizePass): save_data.comp.whitePoint = 10.0 # These overlays modify the underlying texture, so we need to save it out instead of the overlay if overlay == rd.DebugOverlay.ClearBeforeDraw or overlay == rd.DebugOverlay.ClearBeforePass: save_data.resourceId = tex.resourceId self.controller.SaveTexture(save_data, overlay_path) if not rdtest.image_compare(overlay_path, ref_path, tolerance): raise rdtest.TestFailureException("Reference and output image differ for overlay {}".format(str(overlay)), overlay_path, ref_path) rdtest.log.success("Reference and output image are identical for {}".format(str(overlay))) save_data = rd.TextureSave() save_data.resourceId = pipe.GetDepthTarget().resourceId save_data.destType = rd.FileType.PNG save_data.channelExtract = 0 tmp_path = rdtest.get_tmp_path('depth.png') ref_path = self.get_ref_path('depth.png') self.controller.SaveTexture(save_data, tmp_path) if not rdtest.image_compare(tmp_path, ref_path): raise rdtest.TestFailureException("Reference and output image differ for depth {}", tmp_path, ref_path) rdtest.log.success("Reference and output image are identical for depth") save_data.channelExtract = 1 tmp_path = rdtest.get_tmp_path('stencil.png') ref_path = self.get_ref_path('stencil.png') self.controller.SaveTexture(save_data, tmp_path) if not rdtest.image_compare(tmp_path, ref_path): raise rdtest.TestFailureException("Reference and output image differ for stencil {}", tmp_path, ref_path) rdtest.log.success("Reference and output image are identical for stencil") out.Shutdown()
def check_capture(self): action: rd.ActionDescription = self.find_action('glDraw') while action is not None: self.controller.SetFrameEvent(action.eventId, True) self.check_triangle(fore=[0.2, 0.75, 0.2, 1.0]) pipe = self.controller.GetPipelineState() depth = pipe.GetDepthTarget() vp = pipe.GetViewport(0) id = pipe.GetOutputTargets()[0].resourceId mn, mx = self.controller.GetMinMax(id, rd.Subresource(), rd.CompType.Typeless) if not rdtest.value_compare(mn.floatValue, [0.2, 0.2, 0.2, 1.0], eps=1.0/255.0): raise rdtest.TestFailureException( "Minimum color values {} are not as expected".format(mn.floatValue)) if not rdtest.value_compare(mx.floatValue, [0.2, 0.75, 0.2, 1.0], eps=1.0/255.0): raise rdtest.TestFailureException( "Maximum color values {} are not as expected".format(mx.floatValue)) hist = self.controller.GetHistogram(id, rd.Subresource(), rd.CompType.Typeless, 0.199, 0.75, (False, True, False, False)) if hist[0] == 0 or hist[-1] == 0 or any([x > 0 for x in hist[1:-1]]): raise rdtest.TestFailureException( "Green histogram didn't return expected values, values should have landed in first or last bucket") rdtest.log.success('Color Renderbuffer at action {} is working as expected'.format(action.eventId)) if depth.resourceId != rd.ResourceId(): val = self.controller.PickPixel(depth.resourceId, int(0.5 * vp.width), int(0.5 * vp.height), rd.Subresource(), rd.CompType.Typeless) if not rdtest.value_compare(val.floatValue[0], 0.75): raise rdtest.TestFailureException( "Picked value {} in triangle for depth doesn't match expectation".format(val)) mn, mx = self.controller.GetMinMax(depth.resourceId, rd.Subresource(), rd.CompType.Typeless) hist = self.controller.GetHistogram(depth.resourceId, rd.Subresource(), rd.CompType.Typeless, 0.75, 0.9, (True, False, False, False)) if not rdtest.value_compare(mn.floatValue[0], 0.75): raise rdtest.TestFailureException( "Minimum depth values {} are not as expected".format(mn.floatValue)) if not rdtest.value_compare(mx.floatValue[0], 0.9): raise rdtest.TestFailureException( "Maximum depth values {} are not as expected".format(mx.floatValue)) if hist[0] == 0 or hist[-1] == 0 or any([x > 0 for x in hist[1:-1]]): raise rdtest.TestFailureException( "Depth histogram didn't return expected values, values should have landed in first or last bucket") rdtest.log.success('Depth Renderbuffer at action {} is working as expected'.format(action.eventId)) tex_details = self.get_texture(id) if tex_details.msSamp > 1: samples = [] for i in range(tex_details.msSamp): samples.append(self.controller.GetTextureData(id, rd.Subresource(0, 0, i))) for i in range(tex_details.msSamp): for j in range(tex_details.msSamp): if i == j: continue if samples[i] == samples[j]: save_data = rd.TextureSave() save_data.resourceId = id save_data.destType = rd.FileType.PNG save_data.slice.sliceIndex = 0 save_data.mip = 0 img_path0 = rdtest.get_tmp_path('sample{}.png'.format(i)) img_path1 = rdtest.get_tmp_path('sample{}.png'.format(j)) save_data.sample.sampleIndex = i self.controller.SaveTexture(save_data, img_path0) save_data.sample.sampleIndex = j self.controller.SaveTexture(save_data, img_path1) raise rdtest.TestFailureException("Two MSAA samples returned the same data", img_path0, img_path1) action: rd.ActionDescription = self.find_action('glDraw', action.eventId+1) rdtest.log.success('All renderbuffers checked and rendered correctly')
def check_capture(self): draw: rd.DrawcallDescription = self.find_draw("Degenerate") self.controller.SetFrameEvent(draw.next.eventId, True) pipe: rd.VKState = self.controller.GetVulkanPipelineState() if pipe.multisample.rasterSamples != 4: raise rdtest.TestFailureException( "MSAA sample count is {}, not 1".format( pipe.multisample.rasterSamples)) sampleLoc: rd.VKSampleLocations = pipe.multisample.sampleLocations if sampleLoc.gridWidth != 1: raise rdtest.TestFailureException( "Sample locations grid width is {}, not 1".format( sampleLoc.gridWidth)) if sampleLoc.gridHeight != 1: raise rdtest.TestFailureException( "Sample locations grid height is {}, not 1".format( sampleLoc.gridHeight)) # [0] and [1] should be identical, as should [2] and [3], but they should be different from each other if not sampleLoc.customLocations[0] == sampleLoc.customLocations[1]: raise rdtest.TestFailureException( "In degenerate case, sample locations [0] and [1] don't match: {} vs {}" .format(sampleLoc.customLocations[0], sampleLoc.customLocations[1])) if not sampleLoc.customLocations[2] == sampleLoc.customLocations[3]: raise rdtest.TestFailureException( "In degenerate case, sample locations [2] and [3] don't match: {} vs {}" .format(sampleLoc.customLocations[2], sampleLoc.customLocations[3])) if sampleLoc.customLocations[1] == sampleLoc.customLocations[2]: raise rdtest.TestFailureException( "In degenerate case, sample locations [1] and [2] DO match: {} vs {}" .format(sampleLoc.customLocations[1], sampleLoc.customLocations[2])) draw: rd.DrawcallDescription = self.find_draw("Rotated") self.controller.SetFrameEvent(draw.next.eventId, True) pipe: rd.VKState = self.controller.GetVulkanPipelineState() if pipe.multisample.rasterSamples != 4: raise rdtest.TestFailureException( "MSAA sample count is {}, not 1".format( pipe.multisample.rasterSamples)) sampleLoc: rd.VKSampleLocations = pipe.multisample.sampleLocations if sampleLoc.gridWidth != 1: raise rdtest.TestFailureException( "Sample locations grid width is {}, not 1".format( sampleLoc.gridWidth)) if sampleLoc.gridHeight != 1: raise rdtest.TestFailureException( "Sample locations grid height is {}, not 1".format( sampleLoc.gridHeight)) # All sample locations should be unique if sampleLoc.customLocations[0] == sampleLoc.customLocations[1]: raise rdtest.TestFailureException( "In rotated case, sample locations [0] and [1] DO match: {} vs {}" .format(sampleLoc.customLocations[0], sampleLoc.customLocations[1])) if sampleLoc.customLocations[1] == sampleLoc.customLocations[2]: raise rdtest.TestFailureException( "In rotated case, sample locations [1] and [2] DO match: {} vs {}" .format(sampleLoc.customLocations[1], sampleLoc.customLocations[2])) if sampleLoc.customLocations[2] == sampleLoc.customLocations[3]: raise rdtest.TestFailureException( "In rotated case, sample locations [2] and [3] DO match: {} vs {}" .format(sampleLoc.customLocations[2], sampleLoc.customLocations[3])) rdtest.log.success("Pipeline state is correct") # Grab the multisampled image's ID here save_data = rd.TextureSave() curpass: rd.VKCurrentPass = pipe.currentPass save_data.resourceId = curpass.framebuffer.attachments[ curpass.renderpass.colorAttachments[0]].imageResourceId save_data.destType = rd.FileType.PNG save_data.sample.mapToArray = False dim = (0, 0) texs = self.controller.GetTextures() for tex in texs: if tex.resourceId == save_data.resourceId: dim = (tex.width, tex.height) if dim == (0, 0): raise rdtest.TestFailureException( "Couldn't get dimensions of texture") last_draw: rd.DrawcallDescription = self.get_last_draw() self.controller.SetFrameEvent(last_draw.eventId, True) # Due to the variability of rasterization between implementations or even drivers, # we don't want to check against a 'known good'. # So instead we verify that at the first degenerate draw each pair of two sample's images are identical and that # in the rotated grid case each sample's image is distinct. # In future we could also check that the degenerate case 'stretches' the triangle up, as with the way the # geometry is defined the second sample image should be a superset (i.e. strictly more samples covered). rotated_paths = [] degenerate_paths = [] for sample in range(0, 4): tmp_path = rdtest.get_tmp_path('sample{}.png'.format(sample)) degenerate_path = rdtest.get_tmp_path( 'degenerate{}.png'.format(sample)) rotated_path = rdtest.get_tmp_path('rotated{}.png'.format(sample)) rotated_paths.append(rotated_path) degenerate_paths.append(degenerate_path) save_data.sample.sampleIndex = sample self.controller.SaveTexture(save_data, tmp_path) try: img = Image.open(tmp_path) except Exception as ex: raise FileNotFoundError("Can't open {}".format( rdtest.sanitise_filename(tmp_path))) img.crop((0, 0, dim[0] / 2, dim[1])).save(degenerate_path) img.crop((dim[0] / 2, 0, dim[0], dim[1])).save(rotated_path) # first two degenerate images should be identical, as should the last two, and they should be different. if not rdtest.image_compare(degenerate_paths[0], degenerate_paths[1], 0): raise rdtest.TestFailureException( "Degenerate grid sample 0 and 1 are different", degenerate_paths[0], degenerate_paths[1]) if not rdtest.image_compare(degenerate_paths[2], degenerate_paths[3], 0): raise rdtest.TestFailureException( "Degenerate grid sample 2 and 3 are different", degenerate_paths[2], degenerate_paths[3]) if rdtest.image_compare(degenerate_paths[1], degenerate_paths[2], 0): raise rdtest.TestFailureException( "Degenerate grid sample 1 and 2 are identical", degenerate_paths[1], degenerate_paths[2]) rdtest.log.success("Degenerate grid sample images are as expected") # all rotated images should be different for A in range(0, 4): for B in range(A + 1, 4): if rdtest.image_compare(rotated_paths[A], rotated_paths[B], 0): raise rdtest.TestFailureException( "Rotated grid sample {} and {} are identical".format( A, B), rotated_paths[A], rotated_paths[B]) rdtest.log.success("Rotated grid sample images are as expected")
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 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 run(self): controller = self.controller drawcalls = controller.GetDrawcalls() relevant_drawcalls, capture_type = self.extractRelevantCalls(drawcalls) print(f"Scrapping capture from {capture_type}...") if MAX_BLOCKS <= 0: max_drawcall = len(relevant_drawcalls) else: max_drawcall = min(MAX_BLOCKS, len(relevant_drawcalls)) for drawcallId, draw in enumerate(relevant_drawcalls[:max_drawcall]): print("Draw call: " + draw.name) controller.SetFrameEvent(draw.eventId, True) state = controller.GetPipelineState() ib = state.GetIBuffer() vbs = state.GetVBuffers() attrs = state.GetVertexInputs() meshes = [makeMeshData(attr, ib, vbs, draw) for attr in attrs] try: # Position m = meshes[0] m.fetchTriangle(controller) indices = m.fetchIndices(controller) with open("{}{:05d}-indices.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(indices, file) unpacked = m.fetchData(controller) with open("{}{:05d}-positions.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(unpacked, file) # UV m = meshes[2 if capture_type == "Google Earth" else 1] m.fetchTriangle(controller) unpacked = m.fetchData(controller) with open("{}{:05d}-uv.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(unpacked, file) except RuntimeError as err: print("(Skipping: {})".format(err)) continue # Vertex Shader Constants shader = state.GetShader(rd.ShaderStage.Vertex) ep = state.GetShaderEntryPoint(rd.ShaderStage.Vertex) ref = state.GetShaderReflection(rd.ShaderStage.Vertex) constants = self.getVertexShaderConstants(draw, state=state) constants["DrawCall"] = { "topology": 'TRIANGLE_STRIP' if draw.topology == rd.Topology.TriangleStrip else 'TRIANGLES', "type": capture_type } with open("{}{:05d}-constants.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(constants, file) # Texture # dirty bindpoints = state.GetBindpointMapping(rd.ShaderStage.Fragment) texture_bind = bindpoints.samplers[-1].bind resources = state.GetReadOnlyResources(rd.ShaderStage.Fragment) rid = resources[texture_bind].resources[0].resourceId texsave = rd.TextureSave() texsave.resourceId = rid texsave.mip = 0 texsave.slice.sliceIndex = 0 texsave.alpha = rd.AlphaMapping.Preserve texsave.destType = rd.FileType.PNG controller.SaveTexture(texsave, "{}{:05d}-texture.png".format(FILEPREFIX, drawcallId))
def check_capture(self): self.check_final_backbuffer() for level in ["Primary", "Secondary"]: rdtest.log.print("Checking {} indirect calls".format(level)) dispatches = self.find_draw("{}: Dispatches".format(level)) # Set up a ReplayOutput and TextureSave for quickly testing the drawcall highlight overlay out: rd.ReplayOutput = self.controller.CreateOutput( rd.CreateHeadlessWindowingData(), rd.ReplayOutputType.Texture) self.check(out is not None) out.SetDimensions(100, 100) tex = rd.TextureDisplay() tex.overlay = rd.DebugOverlay.Drawcall save_data = rd.TextureSave() save_data.destType = rd.FileType.PNG # Rewind to the start of the capture draw: rd.DrawcallDescription = dispatches.children[0] while draw.previous is not None: draw = draw.previous # Ensure we can select all draws while draw is not None: self.controller.SetFrameEvent(draw.eventId, False) draw = draw.next rdtest.log.success("Selected all {} draws".format(level)) self.check(dispatches and len(dispatches.children) == 3) self.check(dispatches.children[0].dispatchDimension == [0, 0, 0]) self.check(dispatches.children[1].dispatchDimension == [1, 1, 1]) self.check(dispatches.children[2].dispatchDimension == [3, 4, 5]) rdtest.log.success( "{} Indirect dispatches are the correct dimensions".format( level)) self.controller.SetFrameEvent(dispatches.children[2].eventId, False) pipe: rd.PipeState = self.controller.GetPipelineState() ssbo: rd.BoundResource = pipe.GetReadWriteResources( rd.ShaderStage.Compute)[0].resources[0] data: bytes = self.controller.GetBufferData(ssbo.resourceId, 0, 0) rdtest.log.print("Got {} bytes of uints".format(len(data))) uints = [ struct.unpack_from('=4L', data, offs) for offs in range(0, len(data), 16) ] for x in range(0, 6): # 3 groups of 2 threads each for y in range(0, 8): # 3 groups of 2 threads each for z in range(0, 5): # 5 groups of 1 thread each idx = 100 + z * 8 * 6 + y * 6 + x if not rdtest.value_compare(uints[idx], [x, y, z, 12345]): raise rdtest.TestFailureException( 'expected thread index data @ {},{},{}: {} is not as expected: {}' .format(x, y, z, uints[idx], [x, y, z, 12345])) rdtest.log.success( "Dispatched buffer contents are as expected for {}".format( level)) empties = self.find_draw("{}: Empty draws".format(level)) self.check(empties and len(empties.children) == 2) draw: rd.DrawcallDescription for draw in empties.children: self.check(draw.numIndices == 0) self.check(draw.numInstances == 0) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have empty PostVS postvs_data = self.get_postvs(rd.MeshDataStage.VSOut, 0, 1) self.check(len(postvs_data) == 0) self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} empty draws are empty".format(level)) indirects = self.find_draw("{}: Indirect draws".format(level)) self.check('vkCmdDrawIndirect' in indirects.children[0].name) self.check( 'vkCmdDrawIndexedIndirect' in indirects.children[1].name) self.check(len(indirects.children[1].children) == 2) rdtest.log.success( "Correct number of {} indirect draws".format(level)) # vkCmdDrawIndirect(...) draw = indirects.children[0] self.check(draw.numIndices == 3) self.check(draw.numInstances == 2) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have PostVS as expected postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex.gl_Position': [-0.8, -0.5, 0.0, 1.0] }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex.gl_Position': [-0.7, -0.8, 0.0, 1.0] }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex.gl_Position': [-0.6, -0.5, 0.0, 1.0] }, } self.check_mesh_data(postvs_ref, postvs_data) self.check(len(postvs_data) == len( postvs_ref)) # We shouldn't have any extra vertices self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format(level, draw.name)) # vkCmdDrawIndexedIndirect[0](...) draw = indirects.children[1].children[0] self.check(draw.numIndices == 3) self.check(draw.numInstances == 3) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have PostVS as expected postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) # These indices are the *output* indices, which have been rebased/remapped, so are not the same as the input # indices postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex.gl_Position': [-0.6, -0.5, 0.0, 1.0] }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex.gl_Position': [-0.5, -0.8, 0.0, 1.0] }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex.gl_Position': [-0.4, -0.5, 0.0, 1.0] }, } self.check_mesh_data(postvs_ref, postvs_data) self.check(len(postvs_data) == len( postvs_ref)) # We shouldn't have any extra vertices self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format(level, draw.name)) # vkCmdDrawIndexedIndirect[1](...) draw = indirects.children[1].children[1] self.check(draw.numIndices == 6) self.check(draw.numInstances == 2) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have PostVS as expected postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex.gl_Position': [-0.4, -0.5, 0.0, 1.0] }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex.gl_Position': [-0.3, -0.8, 0.0, 1.0] }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex.gl_Position': [-0.2, -0.8, 0.0, 1.0] }, 3: { 'vtx': 3, 'idx': 3, 'gl_PerVertex.gl_Position': [-0.1, -0.5, 0.0, 1.0] }, 4: { 'vtx': 4, 'idx': 4, 'gl_PerVertex.gl_Position': [0.0, -0.8, 0.0, 1.0] }, 5: { 'vtx': 5, 'idx': 5, 'gl_PerVertex.gl_Position': [0.1, -0.8, 0.0, 1.0] }, } self.check_mesh_data(postvs_ref, postvs_data) self.check(len(postvs_data) == len( postvs_ref)) # We shouldn't have any extra vertices self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format(level, draw.name)) indirect_count_root = self.find_draw( "{}: KHR_draw_indirect_count".format(level)) if indirect_count_root is not None: self.check(indirect_count_root.children[0].name == '{}: Empty count draws'.format(level)) self.check(indirect_count_root.children[1].name == '{}: Indirect count draws'.format(level)) empties = indirect_count_root.children[0] self.check(empties and len(empties.children) == 2) draw: rd.DrawcallDescription for draw in empties.children: self.check(draw.numIndices == 0) self.check(draw.numInstances == 0) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have empty PostVS postvs_data = self.get_postvs(rd.MeshDataStage.VSOut, 0, 1) self.check(len(postvs_data) == 0) self.check_overlay(draw.eventId, out, tex, save_data) # vkCmdDrawIndirectCountKHR draw_indirect = indirect_count_root.children[1].children[0] self.check(draw_indirect and len(draw_indirect.children) == 1) # vkCmdDrawIndirectCountKHR[0] draw = draw_indirect.children[0] self.check(draw.numIndices == 3) self.check(draw.numInstances == 4) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have PostVS as expected postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) # These indices are the *output* indices, which have been rebased/remapped, so are not the same as the input # indices postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex.gl_Position': [-0.8, 0.5, 0.0, 1.0] }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex.gl_Position': [-0.7, 0.2, 0.0, 1.0] }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex.gl_Position': [-0.6, 0.5, 0.0, 1.0] }, } self.check_mesh_data(postvs_ref, postvs_data) self.check(len(postvs_data) == len( postvs_ref)) # We shouldn't have any extra vertices self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format( level, draw.name)) # vkCmdDrawIndexedIndirectCountKHR draw_indirect = indirect_count_root.children[1].children[1] self.check(draw_indirect and len(draw_indirect.children) == 3) # vkCmdDrawIndirectCountKHR[0] draw = draw_indirect.children[0] self.check(draw.numIndices == 3) self.check(draw.numInstances == 1) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have PostVS as expected postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) # These indices are the *output* indices, which have been rebased/remapped, so are not the same as the input # indices postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex.gl_Position': [-0.6, 0.5, 0.0, 1.0] }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex.gl_Position': [-0.5, 0.2, 0.0, 1.0] }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex.gl_Position': [-0.4, 0.5, 0.0, 1.0] }, } self.check_mesh_data(postvs_ref, postvs_data) self.check(len(postvs_data) == len( postvs_ref)) # We shouldn't have any extra vertices self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format( level, draw.name)) # vkCmdDrawIndirectCountKHR[1] draw = draw_indirect.children[1] self.check(draw.numIndices == 0) self.check(draw.numInstances == 0) self.controller.SetFrameEvent(draw.eventId, False) postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) self.check(len(postvs_data) == 0) self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format( level, draw.name)) # vkCmdDrawIndirectCountKHR[2] draw = draw_indirect.children[2] self.check(draw.numIndices == 6) self.check(draw.numInstances == 2) self.controller.SetFrameEvent(draw.eventId, False) # Check that we have PostVS as expected postvs_data = self.get_postvs(rd.MeshDataStage.VSOut) # These indices are the *output* indices, which have been rebased/remapped, so are not the same as the input # indices postvs_ref = { 0: { 'vtx': 0, 'idx': 0, 'gl_PerVertex.gl_Position': [-0.4, 0.5, 0.0, 1.0] }, 1: { 'vtx': 1, 'idx': 1, 'gl_PerVertex.gl_Position': [-0.3, 0.2, 0.0, 1.0] }, 2: { 'vtx': 2, 'idx': 2, 'gl_PerVertex.gl_Position': [-0.2, 0.2, 0.0, 1.0] }, 3: { 'vtx': 3, 'idx': 3, 'gl_PerVertex.gl_Position': [-0.1, 0.5, 0.0, 1.0] }, 4: { 'vtx': 4, 'idx': 4, 'gl_PerVertex.gl_Position': [0.0, 0.2, 0.0, 1.0] }, 5: { 'vtx': 5, 'idx': 5, 'gl_PerVertex.gl_Position': [0.1, 0.2, 0.0, 1.0] }, } self.check_mesh_data(postvs_ref, postvs_data) self.check(len(postvs_data) == len( postvs_ref)) # We shouldn't have any extra vertices self.check_overlay(draw.eventId, out, tex, save_data) rdtest.log.success("{} {} is as expected".format( level, draw.name)) else: rdtest.log.print("KHR_draw_indirect_count not tested")
def check_capture(self): # Make an output so we can pick pixels out: rd.ReplayOutput = self.controller.CreateOutput( rd.CreateHeadlessWindowingData(100, 100), rd.ReplayOutputType.Texture) self.check(out is not None) tex = rd.TextureDisplay() # At each draw, the centre pixel of the viewport should be green draw = self.get_first_draw() while draw is not None: self.controller.SetFrameEvent(draw.eventId, False) if draw.flags & rd.DrawFlags.Drawcall: tex.resourceId = self.controller.GetPipelineState( ).GetOutputTargets()[0].resourceId out.SetTextureDisplay(tex) view: rd.Viewport = self.controller.GetPipelineState( ).GetViewport(0) x, y = int(view.x + view.width / 2), int(view.y + view.height / 2) # convert to top-left co-ordinates for use with PickPixel y = self.get_texture(tex.resourceId).height - y picked: rd.PixelValue = out.PickPixel(tex.resourceId, False, x, y, 0, 0, 0) if not rdtest.value_compare(picked.floatValue, [0.0, 1.0, 0.0, 1.0]): raise rdtest.TestFailureException( "Picked value {} at {} doesn't match expected green". format(picked.floatValue, (x, y))) draw = draw.next rdtest.log.success("Draws are all green") # Now save the backbuffer to disk ref_path = rdtest.get_tmp_path('backbuffer.png') save_data = rd.TextureSave() save_data.resourceId = tex.resourceId save_data.destType = rd.FileType.PNG self.controller.SaveTexture(save_data, ref_path) # Open the capture and grab the thumbnail, check that it is all green too (dirty way of verifying we didn't # break in-app updates but somehow end up with the right data) cap = rd.OpenCaptureFile() # Open a particular file status = cap.OpenFile(self.capture_filename, '', None) # Make sure the file opened successfully if status != rd.ReplayStatus.Succeeded: cap.Shutdown() raise rdtest.TestFailureException("Couldn't open '{}': {}".format( self.capture_filename, str(status))) thumb: rd.Thumbnail = cap.GetThumbnail(rd.FileType.PNG, 0) tmp_path = rdtest.get_tmp_path('thumbnail.png') with open(tmp_path, 'wb') as f: f.write(thumb.data) # The original thumbnail should also be identical, since we have the uncompressed extended thumbnail. if not rdtest.png_compare(tmp_path, ref_path): raise rdtest.TestFailureException( "Reference backbuffer and thumbnail image differ", tmp_path, ref_path) rdtest.log.success("Thumbnail is identical to reference") out.Shutdown()
def check_capture(self): action: rd.ActionDescription = self.find_action("Degenerate") self.controller.SetFrameEvent(action.next.eventId, True) pipe: rd.VKState = self.controller.GetVulkanPipelineState() if pipe.multisample.rasterSamples != 4: raise rdtest.TestFailureException("MSAA sample count is {}, not 1".format(pipe.multisample.rasterSamples)) sampleLoc: rd.VKSampleLocations = pipe.multisample.sampleLocations if sampleLoc.gridWidth != 1: raise rdtest.TestFailureException("Sample locations grid width is {}, not 1".format(sampleLoc.gridWidth)) if sampleLoc.gridHeight != 1: raise rdtest.TestFailureException("Sample locations grid height is {}, not 1".format(sampleLoc.gridHeight)) # [0] and [1] should be identical, as should [2] and [3], but they should be different from each other if not sampleLoc.customLocations[0] == sampleLoc.customLocations[1]: raise rdtest.TestFailureException("In degenerate case, sample locations [0] and [1] don't match: {} vs {}" .format(sampleLoc.customLocations[0], sampleLoc.customLocations[1])) if not sampleLoc.customLocations[2] == sampleLoc.customLocations[3]: raise rdtest.TestFailureException("In degenerate case, sample locations [2] and [3] don't match: {} vs {}" .format(sampleLoc.customLocations[2], sampleLoc.customLocations[3])) if sampleLoc.customLocations[1] == sampleLoc.customLocations[2]: raise rdtest.TestFailureException("In degenerate case, sample locations [1] and [2] DO match: {} vs {}" .format(sampleLoc.customLocations[1], sampleLoc.customLocations[2])) action: rd.ActionDescription = self.find_action("Rotated") self.controller.SetFrameEvent(action.next.eventId, True) pipe: rd.VKState = self.controller.GetVulkanPipelineState() if pipe.multisample.rasterSamples != 4: raise rdtest.TestFailureException("MSAA sample count is {}, not 1".format(pipe.multisample.rasterSamples)) sampleLoc: rd.VKSampleLocations = pipe.multisample.sampleLocations if sampleLoc.gridWidth != 1: raise rdtest.TestFailureException("Sample locations grid width is {}, not 1".format(sampleLoc.gridWidth)) if sampleLoc.gridHeight != 1: raise rdtest.TestFailureException("Sample locations grid height is {}, not 1".format(sampleLoc.gridHeight)) # All sample locations should be unique if sampleLoc.customLocations[0] == sampleLoc.customLocations[1]: raise rdtest.TestFailureException("In rotated case, sample locations [0] and [1] DO match: {} vs {}" .format(sampleLoc.customLocations[0], sampleLoc.customLocations[1])) if sampleLoc.customLocations[1] == sampleLoc.customLocations[2]: raise rdtest.TestFailureException("In rotated case, sample locations [1] and [2] DO match: {} vs {}" .format(sampleLoc.customLocations[1], sampleLoc.customLocations[2])) if sampleLoc.customLocations[2] == sampleLoc.customLocations[3]: raise rdtest.TestFailureException("In rotated case, sample locations [2] and [3] DO match: {} vs {}" .format(sampleLoc.customLocations[2], sampleLoc.customLocations[3])) rdtest.log.success("Pipeline state is correct") # Grab the multisampled image's ID here save_data = rd.TextureSave() curpass: rd.VKCurrentPass = pipe.currentPass save_data.resourceId = curpass.framebuffer.attachments[curpass.renderpass.colorAttachments[0]].imageResourceId save_data.destType = rd.FileType.PNG save_data.sample.mapToArray = False dim = (0, 0) fmt: rd.ResourceFormat = None texs = self.controller.GetTextures() for tex in texs: tex: rd.TextureDescription if tex.resourceId == save_data.resourceId: dim = (tex.width, tex.height) fmt = tex.format if dim == (0,0): raise rdtest.TestFailureException("Couldn't get dimensions of texture") halfdim = (dim[0] >> 1, dim[1]) if (fmt.type != rd.ResourceFormatType.Regular or fmt.compByteWidth != 1 or fmt.compCount != 4): raise rdtest.TestFailureException("Texture is not RGBA8 as expected: {}".format(fmt.Name())) stride = fmt.compByteWidth * fmt.compCount * dim[0] last_action: rd.ActionDescription = self.get_last_action() self.controller.SetFrameEvent(last_action.eventId, True) # Due to the variability of rasterization between implementations or even drivers, # we don't want to check against a 'known good'. # So instead we verify that at the first degenerate action each pair of two sample's images are identical and that # in the rotated grid case each sample's image is distinct. # In future we could also check that the degenerate case 'stretches' the triangle up, as with the way the # geometry is defined the second sample image should be a superset (i.e. strictly more samples covered). rotated_paths = [] degenerate_paths = [] for sample in range(0, 4): tmp_path = rdtest.get_tmp_path('sample{}.png'.format(sample)) degenerate_path = rdtest.get_tmp_path('degenerate{}.png'.format(sample)) rotated_path = rdtest.get_tmp_path('rotated{}.png'.format(sample)) rotated_paths.append(rotated_path) degenerate_paths.append(degenerate_path) save_data.sample.sampleIndex = sample self.controller.SaveTexture(save_data, tmp_path) combined_data = rdtest.png_load_data(tmp_path) # crop left for degenerate, and crop right for rotated degenerate = [] rotated = [] for row in range(0, dim[1]): srcstart = row * stride len = halfdim[0] * fmt.compCount degenerate.append(combined_data[row][0:len]) rotated.append(combined_data[row][len:]) rdtest.png_save(degenerate_path, degenerate, halfdim, True) rdtest.png_save(rotated_path, rotated, halfdim, True) # first two degenerate images should be identical, as should the last two, and they should be different. if not rdtest.png_compare(degenerate_paths[0], degenerate_paths[1], 0): raise rdtest.TestFailureException("Degenerate grid sample 0 and 1 are different", degenerate_paths[0], degenerate_paths[1]) if not rdtest.png_compare(degenerate_paths[2], degenerate_paths[3], 0): raise rdtest.TestFailureException("Degenerate grid sample 2 and 3 are different", degenerate_paths[2], degenerate_paths[3]) if rdtest.png_compare(degenerate_paths[1], degenerate_paths[2], 0): raise rdtest.TestFailureException("Degenerate grid sample 1 and 2 are identical", degenerate_paths[1], degenerate_paths[2]) rdtest.log.success("Degenerate grid sample images are as expected") # all rotated images should be different for A in range(0, 4): for B in range(A+1, 4): if rdtest.png_compare(rotated_paths[A], rotated_paths[B], 0): raise rdtest.TestFailureException("Rotated grid sample {} and {} are identical".format(A, B), rotated_paths[A], rotated_paths[B]) rdtest.log.success("Rotated grid sample images are 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() tex = rd.TextureDisplay() tex.resourceId = pipe.GetOutputTargets()[0].resourceId # Check the actual output is as expected first. # Background around the outside self.check_pixel_value(tex.resourceId, 0.1, 0.1, [0.2, 0.2, 0.2, 1.0]) self.check_pixel_value(tex.resourceId, 0.8, 0.1, [0.2, 0.2, 0.2, 1.0]) self.check_pixel_value(tex.resourceId, 0.5, 0.95, [0.2, 0.2, 0.2, 1.0]) # Large dark grey triangle self.check_pixel_value(tex.resourceId, 0.5, 0.1, [0.1, 0.1, 0.1, 1.0]) self.check_pixel_value(tex.resourceId, 0.5, 0.9, [0.1, 0.1, 0.1, 1.0]) self.check_pixel_value(tex.resourceId, 0.2, 0.9, [0.1, 0.1, 0.1, 1.0]) self.check_pixel_value(tex.resourceId, 0.8, 0.9, [0.1, 0.1, 0.1, 1.0]) # Red upper half triangle self.check_pixel_value(tex.resourceId, 0.3, 0.4, [1.0, 0.0, 0.0, 1.0]) # Blue lower half triangle self.check_pixel_value(tex.resourceId, 0.3, 0.6, [0.0, 0.0, 1.0, 1.0]) # Floating clipped triangle self.check_pixel_value(tex.resourceId, 335, 140, [0.0, 0.0, 0.0, 1.0]) self.check_pixel_value(tex.resourceId, 340, 140, [0.2, 0.2, 0.2, 1.0]) # Triangle size triangles self.check_pixel_value(tex.resourceId, 200, 51, [1.0, 0.5, 1.0, 1.0]) self.check_pixel_value(tex.resourceId, 200, 65, [1.0, 1.0, 0.0, 1.0]) self.check_pixel_value(tex.resourceId, 200, 79, [0.0, 1.0, 1.0, 1.0]) self.check_pixel_value(tex.resourceId, 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 # Unfortunately line-fill rendering seems to vary too much by IHV, so gives inconsistent results if overlay == rd.DebugOverlay.Wireframe: continue tex.overlay = overlay out.SetTextureDisplay(tex) overlay_path = rdtest.get_tmp_path(str(overlay) + '.png') ref_path = self.get_ref_path(str(overlay) + '.png') save_data = rd.TextureSave() save_data.resourceId = out.GetDebugOverlayTexID() save_data.destType = rd.FileType.PNG save_data.typeCast = rd.CompType.Typeless save_data.comp.blackPoint = 0.0 save_data.comp.whitePoint = 1.0 tolerance = 2 # These overlays return grayscale above 1, so rescale to an expected range. if (overlay == rd.DebugOverlay.QuadOverdrawDraw or overlay == rd.DebugOverlay.QuadOverdrawPass or overlay == rd.DebugOverlay.TriangleSizeDraw or overlay == rd.DebugOverlay.TriangleSizePass): save_data.comp.whitePoint = 10.0 # These overlays modify the underlying texture, so we need to save it out instead of the overlay if overlay == rd.DebugOverlay.ClearBeforeDraw or overlay == rd.DebugOverlay.ClearBeforePass: save_data.resourceId = tex.resourceId save_data.typeCast = rd.CompType.UNormSRGB self.controller.SaveTexture(save_data, overlay_path) if not rdtest.png_compare(overlay_path, ref_path, tolerance): raise rdtest.TestFailureException( "Reference and output image differ for overlay {}".format( str(overlay)), overlay_path, ref_path) rdtest.log.success( "Reference and output image are identical for {}".format( str(overlay))) save_data = rd.TextureSave() save_data.resourceId = pipe.GetDepthTarget().resourceId save_data.destType = rd.FileType.PNG save_data.channelExtract = 0 tmp_path = rdtest.get_tmp_path('depth.png') ref_path = self.get_ref_path('depth.png') self.controller.SaveTexture(save_data, tmp_path) if not rdtest.png_compare(tmp_path, ref_path): raise rdtest.TestFailureException( "Reference and output image differ for depth {}", tmp_path, ref_path) rdtest.log.success( "Reference and output image are identical for depth") save_data.channelExtract = 1 tmp_path = rdtest.get_tmp_path('stencil.png') ref_path = self.get_ref_path('stencil.png') self.controller.SaveTexture(save_data, tmp_path) if not rdtest.png_compare(tmp_path, ref_path): raise rdtest.TestFailureException( "Reference and output image differ for stencil {}", tmp_path, ref_path) rdtest.log.success( "Reference and output image are identical for stencil") self.check_clearbeforedraw_depth(out, pipe.GetDepthTarget().resourceId) 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(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 main(controller): drawcalls = controller.GetDrawcalls() relevant_drawcalls = list_relevant_calls(drawcalls) for drawcallId, draw in enumerate(relevant_drawcalls): print("Draw call: " + draw.name) if not draw.name.startswith("glDrawElements"): print("(Skipping)") continue controller.SetFrameEvent(draw.eventId, True) state = controller.GetPipelineState() ib = state.GetIBuffer() vbs = state.GetVBuffers() attrs = state.GetVertexInputs() meshes = [makeMeshData(attr, ib, vbs, draw) for attr in attrs] try: # Position m = meshes[0] m.fetchTriangle(controller) indices = m.fetchIndices(controller) with open("{}{:05d}-indices.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(indices, file) unpacked = m.fetchData(controller) with open("{}{:05d}-positions.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(unpacked, file) # UV m = meshes[1] m.fetchTriangle(controller) unpacked = m.fetchData(controller) with open("{}{:05d}-uv.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(unpacked, file) except RuntimeError as err: print("(Skipping: {})".format(err)) continue # Vertex Shader Constants shader = state.GetShader(rd.ShaderStage.Vertex) ep = state.GetShaderEntryPoint(rd.ShaderStage.Vertex) ref = state.GetShaderReflection(rd.ShaderStage.Vertex) constants = {} for cb in ref.constantBlocks: block = {} variables = controller.GetCBufferVariableContents( shader, ep, cb.bindPoint, rd.ResourceId.Null(), 0) for var in variables: val = 0 if var.members: val = [] for member in var.members: memval = 0 if member.type == rd.VarType.Float: memval = member.value.fv[:member.rows * member.columns] elif member.type == rd.VarType.Int: memval = member.value.iv[:member.rows * member.columns] # ... val.append(memval) else: if var.type == rd.VarType.Float: val = var.value.fv[:var.rows * var.columns] elif var.type == rd.VarType.Int: val = var.value.iv[:var.rows * var.columns] # ... block[var.name] = val constants[cb.name] = block with open("{}{:05d}-constants.bin".format(FILEPREFIX, drawcallId), 'wb') as file: pickle.dump(constants, file) # Texture # dirty resources = state.GetReadOnlyResources(rd.ShaderStage.Fragment) rid = resources[0].resources[0].resourceId texsave = rd.TextureSave() texsave.resourceId = rid texsave.mip = 0 texsave.slice.sliceIndex = 0 texsave.alpha = rd.AlphaMapping.Preserve texsave.destType = rd.FileType.PNG controller.SaveTexture( texsave, "{}{:05d}-texture.png".format(FILEPREFIX, drawcallId))
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): out: rd.ReplayOutput = self.controller.CreateOutput(rd.CreateHeadlessWindowingData(100, 100), rd.ReplayOutputType.Texture) self.check(out is not None) api: rd.GraphicsAPI = self.controller.GetAPIProperties().pipelineType # Check the actual output is as expected first. for is_msaa in [False, True]: if is_msaa: test_marker: rd.DrawcallDescription = self.find_draw("MSAA Test") else: test_marker: rd.DrawcallDescription = self.find_draw("Normal 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 tex.subresource.sample = 0 # 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("MSAA" if is_msaa else "normal", 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 # Also to be safe we don't run this test on MSAA if not is_msaa: 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 picked: rd.PixelValue = self.controller.PickPixel(overlay_id, x, 150, rd.Subresource(), rd.CompType.Typeless) 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.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], eps=eps) self.check_pixel_value(overlay_id, 350, 50, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], eps=eps) self.check_pixel_value(overlay_id, 50, 250, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], eps=eps) self.check_pixel_value(overlay_id, 350, 250, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], eps=eps) # Passing triangle inside the viewport self.check_pixel_value(overlay_id, 200, 150, [0.2 * 0.4, 1.0 * 0.6 + 0.2 * 0.4, 0.9 * 0.4, 1.0 * 0.6 + 0.4 * 0.4], 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: # This would require extreme command buffer patching to de-MSAA the framebuffer and renderpass if api == rd.GraphicsAPI.Vulkan and is_msaa: rdtest.log.print("Quad overdraw not currently supported on MSAA on Vulkan") continue 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: # This would require extreme command buffer patching to de-MSAA the framebuffer and renderpass if api == rd.GraphicsAPI.Vulkan and is_msaa: rdtest.log.print("Quad overdraw not currently supported on MSAA on Vulkan") continue 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))) if is_msaa: rdtest.log.success("All MSAA overlays are as expected") else: rdtest.log.success("All normal overlays are as expected") # Check the viewport overlay especially view_marker: rd.DrawcallDescription = self.find_draw("Viewport Test") self.controller.SetFrameEvent(view_marker.next.eventId, True) pipe: rd.PipeState = self.controller.GetPipelineState() col_tex: rd.ResourceId = pipe.GetOutputTargets()[0].resourceId 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 viewport draw".format(str(overlay))) tex.resourceId = col_tex tex.overlay = overlay out.SetTextureDisplay(tex) out.Display() eps = 1.0 / 256.0 overlay_id: rd.ResourceId = out.GetDebugOverlayTexID() save_data = rd.TextureSave() save_data.resourceId = overlay_id save_data.destType = rd.FileType.PNG self.controller.SaveTexture(save_data, rdtest.get_tmp_path('overlay.png')) if overlay == rd.DebugOverlay.Drawcall: # The drawcall overlay will show up outside the scissor region self.check_pixel_value(overlay_id, 50, 85, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 50, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 10, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 85, 85, [0.8, 0.1, 0.8, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 5, [0.0, 0.0, 0.0, 0.5], eps=eps) self.check_pixel_value(overlay_id, 95, 85, [0.0, 0.0, 0.0, 0.5], eps=eps) self.check_pixel_value(overlay_id, 80, 30, [0.0, 0.0, 0.0, 0.5], 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 found = False for delta in range(0, 5): try: self.check_pixel_value(overlay_id, 30 + delta, 32, [200.0 / 255.0, 1.0, 0.0, 1.0], eps=eps) found = True break except rdtest.TestFailureException: pass if not found: raise rdtest.TestFailureException("Couldn't find wireframe within scissor") found = False for delta in range(0, 5): try: self.check_pixel_value(overlay_id, 34 + delta, 22, [200.0 / 255.0, 1.0, 0.0, 1.0], eps=eps) found = True break except rdtest.TestFailureException: pass if found: raise rdtest.TestFailureException("Found wireframe outside of scissor") elif overlay == rd.DebugOverlay.Depth or overlay == rd.DebugOverlay.Stencil or overlay == rd.DebugOverlay.BackfaceCull: self.check_pixel_value(overlay_id, 50, 25, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 75, [0.0, 1.0, 0.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 20, [0.0, 1.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 50, 80, [0.0, 1.0, 0.0, 0.0], eps=eps) elif overlay == rd.DebugOverlay.ViewportScissor: # Inside viewport and scissor, passing triangle self.check_pixel_value(overlay_id, 50, 50, [0.2 * 0.4, 1.0 * 0.6 + 0.2 * 0.4, 0.9 * 0.4, 1.0 * 0.6 + 0.4 * 0.4], eps=eps) # Inside viewport and outside scissor self.check_pixel_value(overlay_id, 50, 80, [1.0 * 0.6 + 0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 1.0 * 0.6 + 0.4 * 0.4], eps=eps) elif overlay == rd.DebugOverlay.QuadOverdrawDraw: self.check_pixel_value(overlay_id, 50, 50, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 15, [0.0, 0.0, 0.0, 0.0], eps=eps) elif overlay == rd.DebugOverlay.QuadOverdrawPass: self.check_pixel_value(overlay_id, 50, 50, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 15, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 200, 270, [1.0, 1.0, 1.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 280, [0.0, 0.0, 0.0, 0.0], eps=eps) elif overlay == rd.DebugOverlay.TriangleSizeDraw: eps = 1.0 self.check_pixel_value(overlay_id, 50, 50, [5408.0, 5408.0, 5408.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 15, [0.0, 0.0, 0.0, 0.0], eps=eps) elif overlay == rd.DebugOverlay.TriangleSizePass: eps = 1.0 self.check_pixel_value(overlay_id, 50, 50, [5408.0, 5408.0, 5408.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 50, 15, [0.0, 0.0, 0.0, 0.0], eps=eps) self.check_pixel_value(overlay_id, 200, 270, [43072.0, 43072.0, 43072.0, 1.0], eps=eps) self.check_pixel_value(overlay_id, 200, 280, [0.0, 0.0, 0.0, 0.0], eps=eps) rdtest.log.success("Picked pixels are as expected for {}".format(str(overlay))) rdtest.log.success("Overlays are as expected around viewport/scissor behaviour") test_marker: rd.DrawcallDescription = self.find_draw("Normal Test") # Now check clear-before-X by hand, for colour and for depth self.controller.SetFrameEvent(test_marker.next.eventId, True) 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) rdtest.log.success("Colour and depth at end are correct") # 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, pipe.GetOutputTargets()[0].firstSlice, 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.print("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 >> shift, 150 >> shift, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(0, 0, 0)) self.check_pixel_value(overlay_id, 197 >> shift, 147 >> shift, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(0, 0, 0)) self.check_pixel_value(overlay_id, 203 >> shift, 153 >> shift, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(0, 0, 0)) # Also for array slice 0 on this mip self.check_pixel_value(overlay_id, 200 >> shift, 150 >> shift, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(mip, 0, 0)) self.check_pixel_value(overlay_id, 197 >> shift, 147 >> shift, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(mip, 0, 0)) self.check_pixel_value(overlay_id, 203 >> shift, 153 >> shift, [0.0, 0.0, 0.0, 0.0], sub=rd.Subresource(mip, 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, 20 >> shift, 15 >> shift, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 80 >> shift, 15 >> shift, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 20 >> shift, 60 >> shift, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 80 >> shift, 60 >> shift, [0.2 * 0.4, 0.2 * 0.4, 0.9 * 0.4, 0.4 * 0.4], sub=sub, eps=eps) self.check_pixel_value(overlay_id, 50 >> shift, 36 >> shift, [0.2 * 0.4, 1.0 * 0.6 + 0.2 * 0.4, 0.9 * 0.4, 1.0 * 0.6 + 0.4 * 0.4], 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()