Ejemplo n.º 1
0
def open_helper(file: str, mode: str):
    """
    Helper function to open a file with UTF-8 format, ignoring errors.
    Wraps around runspv.open_helper(), which wraps open() in turn.
    :param file: the file to open - same as the file argument of open().
    :param mode: the mode to open the file in - same as the mode argument of open().
    :return: a File object opened in UTF-8 format and ignoring errors.
    """
    return runspv.open_helper(file, mode)
Ejemplo n.º 2
0
def main_helper(args) -> None:
    """
    Turn a GraphicsFuzz .json file into a spirv-fuzz .facts file, with one fact per word of uniform
    data.

    :param args: command-line arguments
    :return: None
    """

    parser = argparse.ArgumentParser()

    # Required arguments
    parser.add_argument('shader_job', help='Shader job (.json) file.')

    parser.add_argument('output_file', help='Output file for facts.')

    args = parser.parse_args(args)

    # Generate uniform facts from .json file
    fact_list = []  # type: List[dict]

    # Turn the information about uniform values into facts for spirv-fuzz.
    with runspv.open_helper(args.shader_job, 'r') as f:
        j = json.load(f)  # type: Dict[str, Any]
    for name, entry in j.items():

        # Skip compute shader data
        if name == "$compute":
            continue

        scalar_uniform_functions = ['glUniform1i', 'glUniform1f']
        vector_uniform_functions = [
            'glUniform2i', 'glUniform3i', 'glUniform4i', 'glUniform2f',
            'glUniform3f', 'glUniform4f'
        ]

        if not (entry["func"] in scalar_uniform_functions
                or entry["func"] in vector_uniform_functions):
            print("Ignoring unsupported uniform function " + entry["func"])
            continue

        # Make a separate fact for every component value of each uniform.
        for i in range(0, len(entry["args"])):
            # Every uniform is in its own struct, so we index into the first element of that struct
            # using index 0.  If the uniform is a vector, we need to further index into the vector.
            if entry["func"] in scalar_uniform_functions:
                # The uniform is a scalar.
                assert i == 0
                index_list = [0]
            else:
                assert entry["func"] in vector_uniform_functions
                # The uniform is a vector, so we have two levels of indexing.
                index_list = [0, i]

            # We need to pass the component value as an integer.  If it is a float, we need to
            # reinterpret its bits as an integer.
            int_representation = entry["args"][i]
            if isinstance(int_representation, float):
                int_representation = struct.unpack(
                    '<I', struct.pack('<f', entry["args"][i]))[0]

            uniform_buffer_element_descriptor = dict(descriptorSet=0,
                                                     binding=entry["binding"],
                                                     index=index_list)

            fact_constant_uniform = dict(uniformBufferElementDescriptor=
                                         uniform_buffer_element_descriptor,
                                         constantWord=[int_representation])
            fact_list.append(dict(constantUniformFact=fact_constant_uniform))

    with runspv.open_helper(args.output_file, 'w') as f:
        f.write(json.dumps(dict(fact=fact_list), indent=1, sort_keys=True))
Ejemplo n.º 3
0
def write_to_file(content, filename):
    with runspv.open_helper(filename, 'w') as f:
        f.write(content)
Ejemplo n.º 4
0
def main():
    parser = argparse.ArgumentParser()

    # Required arguments
    parser.add_argument(
        'worker',
        help='Worker name to identify to the server')

    parser.add_argument(
        'target',
        help=runspv.TARGET_HELP)

    # Optional arguments
    parser.add_argument(
        '--force',
        action='store_true',
        help=runspv.FORCE_OPTION_HELP)

    parser.add_argument(
        '--legacy-worker',
        action='store_true',
        help=runspv.LEGACY_OPTION_HELP)

    parser.add_argument(
        '--serial',
        help=runspv.SERIAL_OPTION_HELP)

    parser.add_argument(
        '--server',
        default='http://localhost:8080',
        help='Server URL (default: http://localhost:8080 )')

    parser.add_argument(
        '--spirvopt',
        help=runspv.SPIRV_OPT_OPTION_HELP)

    parser.add_argument(
        '--local-shader-job',
        help='Execute a single, locally stored shader job (for debugging), instead of using the '
             'server.')

    args = parser.parse_args()

    spirv_args = None  # type: Optional[List[str]]
    if args.spirvopt:
        spirv_args = args.spirvopt.split()

    # Check the target is known.
    if not (args.target == 'android' or args.target == 'host'):
        raise ValueError('Target must be "android" or "host"')

    # Record whether or not we are targeting Android.
    is_android = (args.target == 'android')

    # Check the optional arguments are consistent with the target.
    if not is_android and args.force:
        raise ValueError('"force" option is only compatible with "android" target')

    if not is_android and args.serial:
        raise ValueError('"serial" option is only compatible with "android" target')

    print('Worker: ' + args.worker)

    server = args.server + '/request'
    print('server: ' + server)

    if args.serial:
        os.environ['ANDROID_SERIAL'] = args.serial

    service = None
    worker = None

    # Get worker info
    worker_info_file = 'worker_info.json'
    remove(worker_info_file)

    worker_info_json_string = '{}'

    try:
        if is_android:
            runspv.dump_info_android_legacy(wait_for_screen=not args.force)
        else:
            runspv.dump_info_host_legacy()

        if not os.path.isfile(worker_info_file):
            raise Exception(
                'Failed to retrieve worker information.  If targeting Android, make sure '
                'the app permission to write to external storage is enabled.'
            )

        with runspv.open_helper(worker_info_file, 'r') as f:
            worker_info_json_string = f.read()

    except Exception as ex:
        if args.legacy_worker:
            raise ex
        else:
            print(ex)
            print('Continuing without worker information.')

    # Main loop
    while True:

        if is_android \
            and 'ANDROID_SERIAL' in os.environ and \
                not is_device_available(os.environ['ANDROID_SERIAL']):
            raise Exception(
                '#### ABORT: device {} is not available (either offline or not connected?)'
                .format(os.environ['ANDROID_SERIAL'])
            )

        # Special case: local shader job for debugging.
        if args.local_shader_job:
            assert args.local_shader_job.endswith('.json'), \
                'Expected local shader job "{}" to end with .json'

            shader_job_prefix = remove_end(args.local_shader_job, '.json')

            fake_job = tt.ImageJob()
            fake_job.name = os.path.basename(shader_job_prefix)

            assert os.path.isfile(args.local_shader_job), \
                'Shader job {} does not exist'.format(args.local_shader_job)
            with runspv.open_helper(args.local_shader_job) as f:
                fake_job.uniformsInfo = f.read()
            if os.path.isfile(shader_job_prefix + '.frag'):
                with runspv.open_helper(shader_job_prefix + '.frag', 'r') as f:
                    fake_job.fragmentSource = f.read()
            if os.path.isfile(shader_job_prefix + '.vert'):
                with runspv.open_helper(shader_job_prefix + '.vert', 'r') as f:
                    fake_job.vertexSource = f.read()
            if os.path.isfile(shader_job_prefix + '.comp'):
                with runspv.open_helper(shader_job_prefix + '.comp', 'r') as f:
                    fake_job.computeSource = f.read()
                fake_job.computeInfo = fake_job.uniformsInfo
            do_image_job(args, fake_job, spirv_args, work_dir='out')
            return

        if not service:
            service, worker = get_service(server, args, worker_info_json_string)

            if not service:
                print("Cannot connect to server, retry in a second...")
                time.sleep(1)
                continue

        assert worker is not None

        os.makedirs(worker, exist_ok=True)

        try:
            job = service.getJob(worker)

            if job.noJob is not None:
                print("No job")
            elif job.skipJob is not None:
                print("Skip job")
                service.jobDone(worker, job)
            else:
                assert job.imageJob is not None

                if job.imageJob.computeSource:
                    print("#### Compute job: " + job.imageJob.name)
                    job.imageJob.result = do_compute_job(
                        args,
                        job.imageJob,
                        spirv_args,
                        work_dir=worker
                    )

                else:
                    print("#### Image job: " + job.imageJob.name)
                    job.imageJob.result = do_image_job(
                        args,
                        job.imageJob,
                        spirv_args,
                        work_dir=worker
                    )

                print("Send back, results status: {}".format(job.imageJob.result.status))
                service.jobDone(worker, job)
                continue

        except (TApplicationException, ConnectionError):
            print("Connection to server lost. Re-initialising client.")
            service = None

        time.sleep(1)
Ejemplo n.º 5
0
def do_compute_job(
    args,
    comp_job: tt.ImageJob,
    spirv_opt_args: Optional[List[str]],
    work_dir: str
) -> tt.ImageJobResult:

    # Output directory is based on the name of job.
    output_dir = os.path.join(work_dir, comp_job.name)

    # Delete and create output directory.
    remove(output_dir)
    os.makedirs(output_dir, exist_ok=True)

    tmpcomp = os.path.join(output_dir, 'tmp.comp')
    tmpjson = os.path.join(output_dir, 'tmp.json')
    log_file = os.path.join(output_dir, runspv.LOGFILE_NAME)
    ssbo_json_file = os.path.join(output_dir, 'ssbo.json')

    # Output files from running the app.
    status_file = os.path.join(output_dir, 'STATUS')

    write_to_file(comp_job.computeSource, tmpcomp)

    write_to_file(comp_job.computeInfo, tmpjson)

    res = tt.ImageJobResult()
    res.log = '#### Start compute shader\n\n'

    assert not args.legacy_worker

    # Set runspv logger. Use try-finally to clean up.

    with runspv.open_helper(log_file, 'w') as f:
        try:
            runspv.log_to_file = f

            runspv.run_compute_amber(
                comp_original=tmpcomp,
                json_file=tmpjson,
                output_dir=output_dir,
                force=args.force,
                is_android=(args.target == 'android'),
                skip_render=comp_job.skipRender,
                spirv_opt_args=spirv_opt_args,
            )
        except Exception as ex:
            runspv.log('Exception: ' + str(ex))
            runspv.log('Removing STATUS file.')
            remove(status_file)
            runspv.log('Continuing.')
        finally:
            runspv.log_to_file = None

    if os.path.isfile(log_file):
        with runspv.open_helper(log_file, 'r') as f:
            res.log += f.read()

    if os.path.isfile(status_file):
        with runspv.open_helper(status_file, 'r') as f:
            status = f.read().rstrip()
        if status == 'SUCCESS':
            res.status = tt.JobStatus.SUCCESS
            assert (os.path.isfile(ssbo_json_file))
            with runspv.open_helper(ssbo_json_file, 'r') as f:
                res.computeOutputs = f.read()

        elif status == 'CRASH':
            res.status = tt.JobStatus.CRASH
        elif status == 'TIMEOUT':
            res.status = tt.JobStatus.TIMEOUT
        else:
            res.log += '\nUnknown status value: ' + status + '\n'
            res.status = tt.JobStatus.UNEXPECTED_ERROR
    else:
        # Not even a status file?
        res.log += '\nNo STATUS file\n'
        res.status = tt.JobStatus.UNEXPECTED_ERROR

    return res
Ejemplo n.º 6
0
def do_image_job(
    args,
    image_job,
    spirv_opt_args: Optional[List[str]],
    work_dir: str
) -> tt.ImageJobResult:

    # Output directory is based on the name of job.
    output_dir = os.path.join(work_dir, image_job.name)

    # Delete and create output directory.
    remove(output_dir)
    os.makedirs(output_dir, exist_ok=True)

    name = image_job.name
    if name.endswith('.frag'):
        name = remove_end(name, '.frag')
    # TODO(324): the worker currently assumes that no vertex shader is present in the image job.

    vert_file = prepare_vert_file(output_dir) if args.legacy_worker else None
    frag_file = os.path.join(output_dir, name + '.frag')
    json_file = os.path.join(output_dir, name + '.json')
    png_file = os.path.join(output_dir, 'image_0.png')
    log_file = os.path.join(output_dir, runspv.LOGFILE_NAME)
    status_file = os.path.join(output_dir, 'STATUS')
    nondet_0 = os.path.join(output_dir, 'nondet0.png')
    nondet_1 = os.path.join(output_dir, 'nondet1.png')

    res = tt.ImageJobResult()

    skip_render = image_job.skipRender

    # Set nice defaults to fields we will not update anyway
    res.passSanityCheck = True
    res.log = 'Start: ' + name + '\n'

    write_to_file(image_job.fragmentSource, frag_file)
    write_to_file(image_job.uniformsInfo, json_file)

    # Set runspv logger. Use try-finally to clean up.

    with runspv.open_helper(log_file, 'w') as f:
        try:
            runspv.log_to_file = f

            if args.legacy_worker:
                if args.target == 'host':
                    runspv.run_image_host_legacy(
                        vert_original=vert_file,
                        frag_original=frag_file,
                        json_file=json_file,
                        output_dir=output_dir,
                        skip_render=skip_render,
                        spirv_opt_args=spirv_opt_args,
                    )
                else:
                    assert args.target == 'android'
                    runspv.run_image_android_legacy(
                        vert_original=vert_file,
                        frag_original=frag_file,
                        json_file=json_file,
                        output_dir=output_dir,
                        force=args.force,
                        skip_render=skip_render,
                        spirv_opt_args=spirv_opt_args,
                    )
            else:
                runspv.run_image_amber(
                    vert_original=vert_file,
                    frag_original=frag_file,
                    json_file=json_file,
                    output_dir=output_dir,
                    force=args.force,
                    is_android=(args.target == 'android'),
                    skip_render=skip_render,
                    spirv_opt_args=spirv_opt_args,
                )
        except Exception as ex:
            runspv.log('Exception: ' + str(ex))
            runspv.log('Removing STATUS file.')
            remove(status_file)
            runspv.log('Continuing.')
        finally:
            runspv.log_to_file = None

    if os.path.isfile(log_file):
        with runspv.open_helper(log_file, 'r') as f:
            res.log += f.read()

    if os.path.isfile(png_file):
        with runspv.open_bin_helper(png_file, 'rb') as f:
            res.PNG = f.read()

    if os.path.isfile(status_file):
        with runspv.open_helper(status_file, 'r') as f:
            status = f.read().rstrip()
        if status == 'SUCCESS':
            res.status = tt.JobStatus.SUCCESS
        elif status == 'CRASH':
            res.status = tt.JobStatus.CRASH
        elif status == 'TIMEOUT':
            res.status = tt.JobStatus.TIMEOUT
        elif status == 'SANITY_ERROR':
            res.status = tt.JobStatus.SANITY_ERROR
        elif status == 'UNEXPECTED_ERROR':
            res.status = tt.JobStatus.UNEXPECTED_ERROR
        elif status == 'NONDET':
            res.status = tt.JobStatus.NONDET
            with runspv.open_bin_helper(nondet_0, 'rb') as f:
                res.PNG = f.read()
            with runspv.open_bin_helper(nondet_1, 'rb') as f:
                res.PNG2 = f.read()
        else:
            res.log += '\nUnknown status value: ' + status + '\n'
            res.status = tt.JobStatus.UNEXPECTED_ERROR
    else:
        # Not even a status file?
        res.log += '\nNo STATUS file\n'
        res.status = tt.JobStatus.UNEXPECTED_ERROR

    return res