static bool node_visitor_dimensions(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref, int32_t node_id, bool * quit, bool * skip_outbound_paths, void * custom_data) { struct flow_node * n = &(*graph_ref)->nodes[node_id]; int32_t outbound_edges = flow_graph_get_edge_count(c, *graph_ref, node_id, false, flow_edgetype_null, false, true); if (outbound_edges == 0) { return true; // Endpoint node - no need. } if (!flow_node_has_dimensions(c, *graph_ref, node_id)) { if (!flow_node_update_state(c, *graph_ref, node_id)) { FLOW_error_return(c); } // If input nodes are populated if ((n->state & flow_node_state_InputDimensionsKnown) > 0) { if (!flow_job_populate_dimensions_for_node(c, job, *graph_ref, node_id, (bool)custom_data)) { FLOW_error_return(c); } } if (!flow_node_has_dimensions(c, *graph_ref, node_id)) { // We couldn't populate this edge, so we sure can't populate others in this direction. // Stop this branch of recursion *skip_outbound_paths = true; } else { flow_job_notify_graph_changed(c, job, *graph_ref); } } return true; }
static bool node_visitor_execute(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref, int32_t node_id, bool * quit, bool * skip_outbound_paths, void * custom_data) { if (!flow_node_update_state(c, *graph_ref, node_id)) { FLOW_error_return(c); } struct flow_node * n = &(*graph_ref)->nodes[node_id]; if (!flow_job_node_is_executed(c, job, *graph_ref, node_id) && n->state == flow_node_state_ReadyForExecution) { uint64_t now = flow_get_high_precision_ticks(); if (!flow_node_execute(c, job, *graph_ref, node_id)) { FLOW_error_return(c); } else { (*graph_ref)->nodes[node_id].ticks_elapsed += (int32_t)(flow_get_high_precision_ticks() - now); n->state = (flow_node_state)(n->state | flow_node_state_Executed); flow_job_notify_node_complete(c, job, *graph_ref, node_id); } } if (!flow_job_node_is_executed(c, job, *graph_ref, node_id)) { // If we couldn't complete this node yet, end this branch. *skip_outbound_paths = true; } else { flow_job_notify_graph_changed(c, job, *graph_ref); } return true; }
static bool node_visitor_post_optimize_flatten(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref, int32_t node_id, bool * quit, bool * skip_outbound_paths, void * custom_data) { if (!flow_node_update_state(c, *graph_ref, node_id)) { FLOW_error_return(c); } struct flow_node * n = &(*graph_ref)->nodes[node_id]; // If input nodes are populated if (n->state == flow_node_state_ReadyForPostOptimizeFlatten) { if (!flow_node_post_optimize_flatten(c, graph_ref, node_id)) { FLOW_error_return(c); } if (!flow_graph_validate(c, *graph_ref)) { FLOW_error_return(c); } *quit = true; *((bool *)custom_data) = true; } else if ((n->state & flow_node_state_InputDimensionsKnown) == 0) { // we can't flatten past missing dimensions *skip_outbound_paths = true; } return true; }
bool load_image(flow_c * c, char * checksum, struct flow_bitmap_bgra ** ref, void * bitmap_owner) { char filename[2048]; if (!create_relative_path(c, false, filename, 2048, "/visuals/%s.png", checksum)) { FLOW_add_to_callstack(c); return false; } struct flow_job * job = flow_job_create(c); if (job == NULL) { FLOW_error_return(c); } size_t bytes_count; uint8_t * bytes = read_all_bytes(c, &bytes_count, filename); if (bytes == NULL) { FLOW_error_return(c); } struct flow_io * input = flow_io_create_from_memory(c, flow_io_mode_read_seekable, bytes, bytes_count, job, NULL); if (input == NULL) { FLOW_error_return(c); } if (!flow_job_add_io(c, job, input, 0, FLOW_INPUT)) { FLOW_error_return(c); } struct flow_graph * g = flow_graph_create(c, 10, 10, 200, 2.0); if (g == NULL) { FLOW_add_to_callstack(c); return false; } int32_t last; last = flow_node_create_decoder(c, &g, -1, 0); last = flow_node_create_bitmap_bgra_reference(c, &g, last, ref); if (flow_context_has_error(c)) { FLOW_add_to_callstack(c); return false; } if (!flow_job_execute(c, job, &g)) { FLOW_add_to_callstack(c); return false; } // Let the bitmap last longer than the job if (!flow_set_owner(c, *ref, bitmap_owner)) { FLOW_add_to_callstack(c); return false; } if (!flow_job_destroy(c, job)) { FLOW_error_return(c); } FLOW_free(c, bytes); return true; }
bool visual_compare_two(flow_c * c, struct flow_bitmap_bgra * a, struct flow_bitmap_bgra * b, const char * comparison_title, double * out_dssim, bool save_bitmaps, bool generate_visual_diff, const char * file_, const char * func_, int line_number, const char * storage_relative_to) { char checksum_a[34]; char checksum_b[34]; // compute checksum of bitmap (two checksums, actually - one for configuration, another for bitmap bytes) if (!checksum_bitmap(c, a, checksum_a, 34)) { FLOW_error(c, flow_status_Invalid_argument); return false; } if (!checksum_bitmap(c, b, checksum_b, 34)) { FLOW_error(c, flow_status_Invalid_argument); return false; } // Compare if (strcmp(checksum_a, checksum_b) == 0) { if (memcmp(a->pixels, b->pixels, a->stride * a->h) == 0) { *out_dssim = 0; return true; // It matches! } else { // Checksum collsion exit(901); } } if (save_bitmaps) { // They differ if (!save_bitmap_to_visuals(c, a, checksum_a, "A", storage_relative_to)) { FLOW_error_return(c); } if (!save_bitmap_to_visuals(c, b, checksum_b, "B", storage_relative_to)) { FLOW_error_return(c); } // Diff the two, generate a third PNG. Also get PSNR metrics from imagemagick if (!diff_images(c, checksum_a, checksum_b, out_dssim, generate_visual_diff && save_bitmaps, storage_relative_to)) { FLOW_error_return(c); } // Dump to HTML= if (!append_html(c, comparison_title, checksum_a, checksum_b, storage_relative_to)) { FLOW_error_return(c); } } return false; }
bool flow_job_link_codecs(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { if (graph_ref == NULL || *graph_ref == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } struct flow_graph * g = *graph_ref; int32_t i; for (i = 0; i < g->next_node_id; i++) { if (g->nodes[i].type == flow_ntype_decoder || g->nodes[i].type == flow_ntype_encoder) { uint8_t * info_bytes = &g->info_bytes[g->nodes[i].info_byte_index]; struct flow_nodeinfo_codec * info = (struct flow_nodeinfo_codec *)info_bytes; if (info->codec == NULL) { info->codec = flow_job_get_codec_instance(c, job, info->placeholder_id); if (info->codec == NULL) FLOW_error_msg(c, flow_status_Graph_invalid, "No matching codec or io found for placeholder id %d (node #%d).", info->placeholder_id, i); } } } return true; }
bool flow_job_force_populate_dimensions(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { // TODO: would be good to verify graph is acyclic. if (!flow_graph_walk(c, job, graph_ref, node_visitor_dimensions, NULL, (void *)true)) { FLOW_error_return(c); } return true; }
bool flow_job_populate_dimensions_where_certain(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { // TODO: would be good to verify graph is acyclic. if (!flow_graph_walk_dependency_wise(c, job, graph_ref, node_visitor_dimensions, NULL, (void *)false)) { FLOW_error_return(c); } return true; }
static bool flow_job_populate_dimensions_for_node(flow_c * c, struct flow_job * job, struct flow_graph * g, int32_t node_id, bool force_estimate) { uint64_t now = flow_get_high_precision_ticks(); if (!flow_node_populate_dimensions(c, g, node_id, force_estimate)) { FLOW_error_return(c); } g->nodes[node_id].ticks_elapsed += (int32_t)(flow_get_high_precision_ticks() - now); return true; }
bool get_image_dimensions(flow_c * c, uint8_t * bytes, size_t bytes_count, int32_t * width, int32_t * height) { struct flow_job * job = flow_job_create(c); if (job == NULL) { FLOW_error_return(c); } struct flow_io * input = flow_io_create_from_memory(c, flow_io_mode_read_seekable, bytes, bytes_count, job, NULL); if (input == NULL) { FLOW_error_return(c); } if (!flow_job_add_io(c, job, input, 0, FLOW_INPUT)) { } struct flow_decoder_info info; if (!flow_job_get_decoder_info(c, job, 0, &info)) { FLOW_error_return(c); } *width = info.frame0_width; *height = info.frame0_height; if (!flow_job_destroy(c, job)) { FLOW_error_return(c); } return true; }
bool flow_graph_pre_optimize_flatten(flow_c * c, struct flow_graph ** graph_ref) { if (*graph_ref == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } bool re_walk; do { re_walk = false; if (!flow_graph_walk_dependency_wise(c, NULL, graph_ref, node_visitor_flatten, NULL, &re_walk)) { FLOW_error_return(c); } } while (re_walk); return true; }
bool flow_graph_optimize(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { if (*graph_ref == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } bool re_walk; do { re_walk = false; if (!flow_graph_walk(c, job, graph_ref, node_visitor_optimize, NULL, &re_walk)) { FLOW_error_return(c); } } while (re_walk); return true; }
bool flow_job_execute_where_certain(flow_c * c, struct flow_job * job, struct flow_graph ** g) { if (*g == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } // //Resets and creates state tracking for this graph // if (!flow_job_create_state(c,job, *g)){ // FLOW_error_return(c); // } if (!flow_graph_walk_dependency_wise(c, job, g, node_visitor_execute, NULL, NULL)) { FLOW_error_return(c); } return true; }
bool visual_compare(flow_c * c, struct flow_bitmap_bgra * bitmap, const char * name, bool store_checksums, size_t off_by_one_byte_differences_permitted, const char * file_, const char * func_, int line_number, const char * storage_relative_to) { char checksum[34]; // compute checksum of bitmap (two checksums, actually - one for configuration, another for bitmap bytes) if (!checksum_bitmap(c, bitmap, checksum, 34)) { FLOW_error(c, flow_status_Invalid_argument); return false; } // Load stored checksum char * stored_checksum = get_checksum_for(c, name, storage_relative_to); // Compare if (stored_checksum != NULL && strcmp(checksum, stored_checksum) == 0) { // Make sure the file is created for later if (!save_bitmap_to_visuals(c, bitmap, checksum, "trusted", storage_relative_to)) { FLOW_error_return(c); } return true; // It matches! } if (stored_checksum == NULL) { // No stored checksum for this name if (store_checksums) { if (!append_checksum(c, checksum, name, storage_relative_to)) { FLOW_error_return(c); } fprintf(stderr, "===============\n%s\nStoring checksum %s, since FLOW_STORE_CHECKSUMS was set.\n ", name, &checksum[0]); } else { fprintf(stderr, "===============\n%s\nThere is no stored checksum for this test; #define " "FLOW_STORE_CHECKSUMS and rerun to set the initial value to %s.\n ", name, &checksum[0]); } fprintf(stderr, "%s:%d in function %s\n", file_, line_number, func_); } else { fprintf(stderr, "===============\n%s\nThe stored checksum [%s] differs from the current result [%s]. Open " "visuals/visuals.html to comapre.\n ", name, stored_checksum, checksum); fprintf(stderr, "%s:%d in function %s\n", file_, line_number, func_); } // The hash differs // Save ours so we can see it if (!save_bitmap_to_visuals(c, bitmap, checksum, "current", storage_relative_to)) { FLOW_error_return(c); } if (stored_checksum == NULL && store_checksums) { // Don't fail the test for an non-stored checksum. We may be trying to commit several new checksums return true; } if (stored_checksum != NULL) { // Try to download "old" png from S3 using the checksums as an address. if (!download_by_checksum(c, stored_checksum, storage_relative_to)) { FLOW_error_return(c); } // First try a byte-by-byte comparison to eliminate floating-point error struct flow_bitmap_bgra * old; if (!load_image(c, stored_checksum, &old, c, storage_relative_to)) { FLOW_error_return(c); } size_t differences; size_t differences_to_print = 0; // 100 size_t total_delta; if (!diff_image_pixels(c, old, bitmap, &differences, &total_delta, 0, off_by_one_byte_differences_permitted + 4096)) { FLOW_error_return(c); } // If the difference is a handful of off-by-one rounding changes, just print the different bytes. if (differences < off_by_one_byte_differences_permitted && total_delta == differences) { if (!diff_image_pixels(c, old, bitmap, &differences, &total_delta, differences_to_print, off_by_one_byte_differences_permitted + 4096)) { FLOW_error_return(c); } fprintf(stderr, "\nA total of %lu bytes (of %lu) differed between the bitmaps (off by one). Less than " "failure threshold of %lu\n", differences, (size_t)old->stride * (size_t)old->h, off_by_one_byte_differences_permitted); return true; } else { double dssim; // Diff the two, generate a third PNG. Also get PSNR metrics from imagemagick if (!diff_images(c, checksum, stored_checksum, &dssim, true, storage_relative_to)) { FLOW_error_return(c); } fprintf(stdout, "DSSIM %.20f between %s and %s\n", dssim, stored_checksum, checksum); // Dump to HTML= if (!append_html(c, name, checksum, stored_checksum, storage_relative_to)) { FLOW_error_return(c); } } flow_bitmap_bgra_destroy(c, old); } return false; }
bool flow_job_execute(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_link_codecs(c, job, graph_ref)) { FLOW_error_return(c); } // States for a node // New // OutboundDimensionsKnown // Flattened // Optimized // LockedForExecution // Executed int32_t passes = 0; while (!flow_job_graph_fully_executed(c, job, *graph_ref)) { if (passes >= job->max_calc_flatten_execute_passes) { FLOW_error(c, flow_status_Maximum_graph_passes_exceeded); return false; } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_graph_pre_optimize_flatten(c, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_graph_optimize(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_graph_post_optimize_flatten(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_execute_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } passes++; if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } } if (job->next_graph_version > 0 && job->render_last_graph && !flow_job_render_graph_to_png(c, job, *graph_ref, job->next_graph_version - 1)) { FLOW_error_return(c); } return true; }