6616 lines
176 KiB
HTML
6616 lines
176 KiB
HTML
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
|
|
<html>
|
|
<title>GPU Dump Viewer</title>
|
|
|
|
<script type="text/javascript">
|
|
|
|
|
|
// -------------------------------------------------------------------- CONSTANTS
|
|
|
|
const k_current_dir = "./";
|
|
|
|
const k_style_scroll_width = 8;
|
|
const k_style_padding_size = 10;
|
|
const k_style_main_div_margin = 15;
|
|
|
|
const k_null_json_ptr = "00000000000000000000";
|
|
|
|
const k_texture_zoom_local_storage = 'texture_zoom';
|
|
|
|
|
|
// -------------------------------------------------------------------- GLOBALS
|
|
|
|
var g_dump_service = {};
|
|
var g_infos = {};
|
|
var g_dump_cvars = {};
|
|
var g_passes = [];
|
|
var g_descs = {};
|
|
|
|
var g_view = null;
|
|
|
|
|
|
// -------------------------------------------------------------------- GLOBALS
|
|
|
|
class IView
|
|
{
|
|
constructor()
|
|
{
|
|
|
|
}
|
|
|
|
setup_html(parent)
|
|
{
|
|
parent.innerHTML = '';
|
|
}
|
|
|
|
resize(ctx)
|
|
{
|
|
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [];
|
|
}
|
|
|
|
release()
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
function set_main_view(new_view)
|
|
{
|
|
var parent_dom = document.getElementById('main_right_pannel');
|
|
if (g_view !== null)
|
|
{
|
|
g_view.release();
|
|
delete g_view;
|
|
parent_dom.innerHTML = '';
|
|
}
|
|
g_view = new_view;
|
|
if (g_view !== null)
|
|
{
|
|
g_view.setup_html(parent_dom);
|
|
onresize_body();
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- FILE LOADING
|
|
|
|
function does_file_exists(relative_path)
|
|
{
|
|
try
|
|
{
|
|
var request = new XMLHttpRequest();
|
|
request.open('HEAD', k_current_dir + relative_path, false);
|
|
request.send(null);
|
|
return request.status != 404;
|
|
}
|
|
catch (error)
|
|
{
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function load_text_file(relative_path)
|
|
{
|
|
try
|
|
{
|
|
var request = new XMLHttpRequest();
|
|
request.open('GET', k_current_dir + relative_path, false);
|
|
request.send(null);
|
|
if (request.status === 0 || request.status === 200)
|
|
{
|
|
return request.responseText;
|
|
}
|
|
add_console_event('error', `couldn't load ${relative_path}: Returned HTTP status ${request.status}`);
|
|
}
|
|
catch (error)
|
|
{
|
|
add_console_event('error', `couldn't load ${relative_path}: ${error}`);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function load_binary_file(relative_path, callback)
|
|
{
|
|
try
|
|
{
|
|
var request = new XMLHttpRequest();
|
|
request.open('GET', k_current_dir + relative_path, true);
|
|
request.responseType = "arraybuffer";
|
|
|
|
request.onload = function(event)
|
|
{
|
|
var array_buffer = request.response;
|
|
if ((request.status === 0 || request.status === 200) && array_buffer)
|
|
{
|
|
callback(array_buffer);
|
|
}
|
|
else
|
|
{
|
|
add_console_event('error', `couldn't load ${relative_path}: Returned HTTP status ${request.status}`);
|
|
callback(null);
|
|
}
|
|
};
|
|
|
|
request.onerror = function() {
|
|
add_console_event('error', `couldn't load ${relative_path}`);
|
|
callback(null);
|
|
};
|
|
|
|
request.send(null);
|
|
}
|
|
catch (error)
|
|
{
|
|
add_console_event('error', `couldn't load ${relative_path}: ${error}`);
|
|
callback(null);
|
|
}
|
|
}
|
|
|
|
function load_resource_binary_file(relative_path, callback)
|
|
{
|
|
return load_binary_file(relative_path, function(raw_texture_data)
|
|
{
|
|
var compression_type = '';
|
|
if (g_dump_service['CompressionName'] == 'Zlib')
|
|
{
|
|
compression_type = 'deflate';
|
|
}
|
|
else if (g_dump_service['CompressionName'] == 'GZip')
|
|
{
|
|
compression_type = 'gzip';
|
|
}
|
|
|
|
if (raw_texture_data === null || compression_type == '')
|
|
{
|
|
return callback(raw_texture_data);
|
|
}
|
|
|
|
var decompressor = new DecompressionStream(compression_type);
|
|
var decompressed_stream = new Blob([raw_texture_data]).stream().pipeThrough(decompressor);
|
|
new Response(decompressed_stream).arrayBuffer().then(callback, function() { callback(null); });
|
|
});
|
|
}
|
|
|
|
function load_json(relative_path)
|
|
{
|
|
var txt = load_text_file(relative_path);
|
|
|
|
if (txt === null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return JSON.parse(load_text_file(relative_path));
|
|
}
|
|
|
|
function load_json_dict_sequence(relative_path)
|
|
{
|
|
var text_file = load_text_file(relative_path);
|
|
if (text_file == '')
|
|
{
|
|
return new Array();
|
|
}
|
|
var dicts = text_file.split("}{");
|
|
var dict_sequence = [];
|
|
dicts.forEach(function(value, index, array) {
|
|
if (!value.startsWith('{'))
|
|
{
|
|
value = '{' + value;
|
|
}
|
|
if (!value.endsWith('}'))
|
|
{
|
|
value = value + '}';
|
|
}
|
|
dict_sequence.push(JSON.parse(value));
|
|
});
|
|
return dict_sequence;
|
|
}
|
|
|
|
function get_resource_desc(unique_resource_name)
|
|
{
|
|
return g_descs[unique_resource_name];
|
|
}
|
|
|
|
function load_structure_metadata(structure_ptr)
|
|
{
|
|
var cache = {};
|
|
|
|
function load_nested_structure_metadata(nested_structure_ptr)
|
|
{
|
|
if (nested_structure_ptr in cache)
|
|
{
|
|
return cache[nested_structure_ptr];
|
|
}
|
|
|
|
var metadata = load_json(`StructuresMetadata/${nested_structure_ptr}.json`);
|
|
cache[nested_structure_ptr] = metadata;
|
|
|
|
for (var member of metadata['Members'])
|
|
{
|
|
if (member['StructMetadata'] == k_null_json_ptr)
|
|
{
|
|
member['StructMetadata'] = null;
|
|
}
|
|
else
|
|
{
|
|
member['StructMetadata'] = load_nested_structure_metadata(member['StructMetadata']);
|
|
}
|
|
}
|
|
|
|
return metadata;
|
|
}
|
|
|
|
return load_nested_structure_metadata(structure_ptr);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- LOCAL STORAGE
|
|
|
|
function set_local_storage(name, value)
|
|
{
|
|
if (typeof value === 'object')
|
|
{
|
|
value = JSON.stringify(value);
|
|
}
|
|
else if (typeof value === 'string')
|
|
{
|
|
// NOP
|
|
}
|
|
else
|
|
{
|
|
console.error('unknown type');
|
|
}
|
|
|
|
localStorage.setItem(name, value);
|
|
}
|
|
|
|
function get_local_storage(name)
|
|
{
|
|
var value = localStorage.getItem(name);
|
|
if (value == null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
if (value[0] === '{')
|
|
{
|
|
value = JSON.parse(value);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function remove_local_storage(name)
|
|
{
|
|
return localStorage.removeItem(name);
|
|
}
|
|
|
|
function clear_local_storage()
|
|
{
|
|
localStorage.clear();
|
|
}
|
|
|
|
function test_local_storage()
|
|
{
|
|
clear_local_storage();
|
|
console.assert(get_local_storage("doesntexist") == null);
|
|
|
|
set_local_storage("hello", "world");
|
|
console.assert(get_local_storage("hello") == "world");
|
|
|
|
remove_local_storage("hello");
|
|
console.assert(get_local_storage("hello") == null);
|
|
|
|
set_local_storage("hello", "world");
|
|
console.assert(get_local_storage("hello") == "world");
|
|
|
|
clear_local_storage();
|
|
console.assert(get_local_storage("hello") == null);
|
|
|
|
set_local_storage("hello", {"world": "cat"});
|
|
console.assert(get_local_storage("hello")["world"] == "cat");
|
|
|
|
clear_local_storage();
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- UTILITY
|
|
|
|
function get_filename(file_path)
|
|
{
|
|
return file_path.split(/(\\|\/)/g).pop();
|
|
}
|
|
|
|
function px_string_to_int(str)
|
|
{
|
|
if (Number.isInteger(str))
|
|
{
|
|
return str;
|
|
}
|
|
if (str.endsWith('px'))
|
|
{
|
|
return Number(str.substring(0, str.length - 2));
|
|
}
|
|
return Number(str);
|
|
}
|
|
|
|
function parse_subresource_unique_name(subresource_unique_name)
|
|
{
|
|
var splitted_name = subresource_unique_name.split(".");
|
|
|
|
var subresource_info = {};
|
|
subresource_info['subresource'] = null;
|
|
subresource_info['array_slice'] = null;
|
|
|
|
if (splitted_name[splitted_name.length - 1].startsWith('mip') || splitted_name[splitted_name.length - 1] == 'stencil')
|
|
{
|
|
subresource_info['subresource'] = splitted_name.pop();
|
|
}
|
|
|
|
if (splitted_name[splitted_name.length - 1].startsWith('[') && splitted_name[splitted_name.length - 1].endsWith(']'))
|
|
{
|
|
var array_slice_bracket = splitted_name.pop();
|
|
subresource_info['array_slice'] = parseInt(array_slice_bracket.substring(1, array_slice_bracket.length - 1));
|
|
}
|
|
|
|
subresource_info['resource'] = splitted_name.join('.');
|
|
|
|
return subresource_info;
|
|
}
|
|
|
|
function get_subresource_unique_name(subresource_info)
|
|
{
|
|
var subresource_unique_name = subresource_info['resource'];
|
|
if (subresource_info['array_slice'] !== null)
|
|
{
|
|
subresource_unique_name += `.[${subresource_info['array_slice']}]`;
|
|
}
|
|
if (subresource_info['subresource'] !== null)
|
|
{
|
|
subresource_unique_name += `.${subresource_info['subresource']}`;
|
|
}
|
|
return subresource_unique_name;
|
|
}
|
|
|
|
function prettify_subresource_unique_name(subresource_info, resource_desc)
|
|
{
|
|
if (!resource_desc)
|
|
{
|
|
return get_subresource_unique_name(subresource_info);
|
|
}
|
|
|
|
var name = resource_desc['Name'];
|
|
|
|
if (subresource_info['array_slice'] !== null)
|
|
{
|
|
name += ` slice[${subresource_info['array_slice']}]`;
|
|
}
|
|
|
|
if (subresource_info['subresource'] !== null && (subresource_info['subresource'] == 'stencil' || resource_desc['NumMips'] > 1))
|
|
{
|
|
name += ` ${subresource_info['subresource']}`;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
function parse_subresource_unique_version_name(subresource_unique_version_name)
|
|
{
|
|
var splitted_name = subresource_unique_version_name.split(".");
|
|
|
|
var pass_ptr = -1;
|
|
var draw_id = -1;
|
|
|
|
var last = splitted_name.pop();
|
|
if (last.startsWith('d'))
|
|
{
|
|
draw_id = parseInt(last.substring(1));
|
|
last = splitted_name.pop();
|
|
}
|
|
|
|
if (last.startsWith('v'))
|
|
{
|
|
pass_ptr = last.substring(1);
|
|
}
|
|
|
|
var subresource_unique_name = splitted_name.join('.');
|
|
var subresource_version_info = parse_subresource_unique_name(subresource_unique_name);
|
|
|
|
subresource_version_info['pass'] = pass_ptr;
|
|
subresource_version_info['draw'] = draw_id;
|
|
return subresource_version_info;
|
|
}
|
|
|
|
function get_subresource_unique_version_name(subresource_version_info)
|
|
{
|
|
if (subresource_version_info['draw'] >= 0)
|
|
{
|
|
return `${get_subresource_unique_name(subresource_version_info)}.v${subresource_version_info['pass']}.d${subresource_version_info['draw']}`;
|
|
}
|
|
return `${get_subresource_unique_name(subresource_version_info)}.v${subresource_version_info['pass']}`;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- SHADER PARAMETERS
|
|
|
|
function iterate_structure_members(root_structure_metadata, callback)
|
|
{
|
|
function iterate_recursive(structure_metadata, offset, cpp_prefix, shader_prefix)
|
|
{
|
|
for (var member of structure_metadata['Members'])
|
|
{
|
|
var base_type = member['BaseType'];
|
|
|
|
if (base_type == 'UBMT_NESTED_STRUCT')
|
|
{
|
|
iterate_recursive(member['StructMetadata'], offset + member['Offset'], cpp_prefix + member['Name'] + '.', shader_prefix + member['Name'] + '_');
|
|
}
|
|
else if (base_type == 'UBMT_INCLUDED_STRUCT')
|
|
{
|
|
iterate_recursive(member['StructMetadata'], offset + member['Offset'], cpp_prefix + member['Name'] + '.', shader_prefix);
|
|
}
|
|
else
|
|
{
|
|
var params = {
|
|
member: member,
|
|
base_type: base_type,
|
|
byte_offset: offset + member['Offset'],
|
|
cpp_name: cpp_prefix + member['Name'],
|
|
shader_name: shader_prefix + member['Name'],
|
|
};
|
|
callback(params);
|
|
}
|
|
}
|
|
}
|
|
|
|
iterate_recursive(root_structure_metadata, /* offset = */ 0, /* cpp_prefix = */ '', /* shader_prefix = */ '');
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- FLOAT ENCODING
|
|
|
|
function decode_float(raw, total_bit_count, exp_bit_count, has_sign)
|
|
{
|
|
var exp_bias = (1 << (exp_bit_count - 1)) - 1;
|
|
var mantissa_bit_count = total_bit_count - exp_bit_count - (has_sign ? 1 : 0);
|
|
|
|
var sign_bit = (raw >> (total_bit_count - 1)) & 0x1;
|
|
var mantissa_bits = (raw >> 0) & ((0x1 << mantissa_bit_count) - 1);
|
|
var exp_bits = (raw >> mantissa_bit_count) & ((0x1 << exp_bit_count) - 1);
|
|
|
|
var is_max_exp = exp_bits == ((0x1 << exp_bit_count) - 1);
|
|
|
|
var is_denormal = exp_bits == 0;
|
|
var is_infinity = is_max_exp && mantissa_bits == 0;
|
|
var is_nan = is_max_exp && mantissa_bits != 0;
|
|
|
|
var exp = exp_bits - exp_bias;
|
|
var mantissa = mantissa_bits * Math.pow(0.5, mantissa_bit_count);
|
|
var sign = (has_sign && (sign_bit == 1)) ? -1 : 1;
|
|
|
|
if (is_nan)
|
|
{
|
|
return 'nan';
|
|
}
|
|
else if (is_infinity)
|
|
{
|
|
return sign == -1 ? '-inf' : '+inf';
|
|
}
|
|
else if (is_denormal)
|
|
{
|
|
var value = sign * mantissa * Math.pow(0.5, exp_bias - 1);
|
|
return value;
|
|
}
|
|
else
|
|
{
|
|
var value = sign * (1.0 + mantissa) * Math.pow(2.0, exp);
|
|
return value;
|
|
}
|
|
}
|
|
|
|
function decode_float10(raw)
|
|
{
|
|
return decode_float(raw, /* total_bit_count = */ 10, /* exp_bit_count = */ 5, /* has_sign = */ false);
|
|
}
|
|
|
|
function decode_float11(raw)
|
|
{
|
|
return decode_float(raw, /* total_bit_count = */ 11, /* exp_bit_count = */ 5, /* has_sign = */ false);
|
|
}
|
|
|
|
function decode_float16(raw)
|
|
{
|
|
return decode_float(raw, /* total_bit_count = */ 16, /* exp_bit_count = */ 5, /* has_sign = */ true);
|
|
}
|
|
|
|
function decode_float32(raw)
|
|
{
|
|
return decode_float(raw, /* total_bit_count = */ 32, /* exp_bit_count = */ 8, /* has_sign = */ true);
|
|
}
|
|
|
|
function test_decode_float()
|
|
{
|
|
var tests = [
|
|
// Zero
|
|
[0x0000, 0.0],
|
|
[0x8000, -0.0],
|
|
|
|
// normals
|
|
[0x4000, 2.0],
|
|
[0xc000, -2.0],
|
|
|
|
// denormals
|
|
[0x0001, 5.960464477539063e-8],
|
|
[0x8001, -5.960464477539063e-8],
|
|
|
|
// exotics
|
|
[0x7c00, '+inf'],
|
|
[0xFc00, '-inf'],
|
|
[0x7c01, 'nan'],
|
|
[0xFc01, 'nan'],
|
|
];
|
|
|
|
for (var i = 0; i < tests.length; i++)
|
|
{
|
|
var encoded = tests[i][0];
|
|
var ref = tests[i][1];
|
|
var computed = decode_float16(encoded);
|
|
|
|
console.assert(computed == ref);
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- PASS ANALYSIS
|
|
|
|
var g_analysis = {};
|
|
|
|
function get_frame_number(pass_data)
|
|
{
|
|
var pass_event_scopes = pass_data['ParentEventScopes'];
|
|
var frame_event_scope_name = pass_event_scopes[pass_event_scopes.length - 1];
|
|
|
|
const regex = new RegExp("Frame (?<FrameNumber>\\d+).*");
|
|
//const regex = /Frame (?<FrameNumber>\d+) .*"/;
|
|
|
|
var found = frame_event_scope_name.match(regex);
|
|
return Number(found.groups['FrameNumber']);
|
|
}
|
|
|
|
function compare_draw_events(ref_draw_event, tested_draw_event)
|
|
{
|
|
ref_draw_event = ref_draw_event.replace(/\d+\.d*/g, '');
|
|
ref_draw_event = ref_draw_event.replace(/\d+/g, '');
|
|
|
|
tested_draw_event = tested_draw_event.replace(/\d+\.d*/g, '');
|
|
tested_draw_event = tested_draw_event.replace(/\d+/g, '');
|
|
|
|
return ref_draw_event == tested_draw_event;
|
|
}
|
|
|
|
function is_pass_output_resource(pass_data, subresource_version_info)
|
|
{
|
|
var subresource_unique_name = get_subresource_unique_name(subresource_version_info);
|
|
|
|
var is_output_resource = null;
|
|
if (pass_data['InputResources'].includes(subresource_unique_name))
|
|
{
|
|
is_output_resource = false;
|
|
}
|
|
else if (pass_data['OutputResources'].includes(subresource_unique_name))
|
|
{
|
|
is_output_resource = true;
|
|
}
|
|
console.assert(is_output_resource !== null);
|
|
return is_output_resource;
|
|
}
|
|
|
|
function find_similar_pass_id_in_frame(current_frame_pass_data, new_frame_id)
|
|
{
|
|
var current_pass_event_scopes = current_frame_pass_data['ParentEventScopes'];
|
|
|
|
for (var pass_id in g_passes)
|
|
{
|
|
var pass_data = g_passes[pass_id];
|
|
if (!compare_draw_events(pass_data['EventName'], current_frame_pass_data['EventName']))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var frame_id = get_frame_number(pass_data);
|
|
if (frame_id != new_frame_id)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var pass_event_scopes = pass_data['ParentEventScopes'];
|
|
if (pass_event_scopes.length != current_pass_event_scopes.length)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
var is_same_parent = true;
|
|
for (var parent_scope_id = 0; parent_scope_id < pass_event_scopes.length - 1; parent_scope_id++)
|
|
{
|
|
is_same_parent = is_same_parent && compare_draw_events(current_pass_event_scopes[parent_scope_id], pass_event_scopes[parent_scope_id]);
|
|
}
|
|
if (!is_same_parent)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return Number(pass_id);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
function find_similar_resource_in_pass(current_frame_pass_data, current_frame_resource_desc, current_frame_subresource_info, new_pass_data)
|
|
{
|
|
var is_output_resource = is_pass_output_resource(current_frame_pass_data, current_frame_subresource_info);
|
|
|
|
var new_pass_resource_list = is_output_resource ? new_pass_data['OutputResources'] : new_pass_data['InputResources'];
|
|
|
|
for (var new_pass_subresource_unique_name of new_pass_resource_list)
|
|
{
|
|
var new_pass_subresource_info = parse_subresource_unique_name(new_pass_subresource_unique_name);
|
|
if (new_pass_subresource_info['subresource'] !== current_frame_subresource_info['subresource'])
|
|
{
|
|
continue;
|
|
}
|
|
if (new_pass_subresource_info['array_slice'] !== current_frame_subresource_info['array_slice'])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var new_pass_resource_desc = get_resource_desc(new_pass_subresource_info['resource']);
|
|
if (new_pass_resource_desc['Name'] !== current_frame_resource_desc['Name'])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return new_pass_subresource_info;
|
|
}
|
|
|
|
// try to find with non matching array_slice
|
|
for (var new_pass_subresource_unique_name of new_pass_resource_list)
|
|
{
|
|
var new_pass_subresource_info = parse_subresource_unique_name(new_pass_subresource_unique_name);
|
|
if (new_pass_subresource_info['subresource'] !== current_frame_subresource_info['subresource'])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var new_pass_resource_desc = get_resource_desc(new_pass_subresource_info['resource']);
|
|
if (new_pass_resource_desc['Name'] !== current_frame_resource_desc['Name'])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
return new_pass_subresource_info;
|
|
}
|
|
|
|
|
|
return null;
|
|
}
|
|
|
|
function analyses_passes()
|
|
{
|
|
var frames = [];
|
|
|
|
for (var pass_id in g_passes)
|
|
{
|
|
var pass_data = g_passes[pass_id];
|
|
|
|
var frame_id = get_frame_number(pass_data);
|
|
|
|
if (!frames.includes(frame_id))
|
|
{
|
|
frames.push(frame_id);
|
|
}
|
|
}
|
|
|
|
g_analysis['FrameList'] = frames;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY PASS
|
|
|
|
function display_pass_hierarchy()
|
|
{
|
|
var search_pass = document.getElementById('pass_search_input').value;
|
|
var search_resource = document.getElementById('resource_search_input').value;
|
|
var html = '';
|
|
var parent_event_scopes = [];
|
|
|
|
g_passes.forEach(function(pass_data, pass_id) {
|
|
var pass_event_scopes = pass_data['ParentEventScopes'];
|
|
|
|
var show_pass = true;
|
|
if (search_pass != '')
|
|
{
|
|
show_pass = pass_data['EventName'].toLowerCase().includes(search_pass.toLowerCase());
|
|
|
|
for (var i = 0; i < pass_event_scopes.length; i++)
|
|
{
|
|
show_pass = show_pass || pass_event_scopes[i].toLowerCase().includes(search_pass.toLowerCase());
|
|
}
|
|
}
|
|
|
|
var show_resource = true;
|
|
if (search_resource != '')
|
|
{
|
|
show_resource = false;
|
|
for (var subresource_unique_name of pass_data['InputResources'])
|
|
{
|
|
var resource_unique_name = parse_subresource_unique_name(subresource_unique_name)['resource'];
|
|
var resource_name = get_resource_desc(resource_unique_name)['Name'];
|
|
show_resource = show_resource || resource_name.toLowerCase().includes(search_resource.toLowerCase());
|
|
}
|
|
for (var subresource_unique_name of pass_data['OutputResources'])
|
|
{
|
|
var resource_unique_name = parse_subresource_unique_name(subresource_unique_name)['resource'];
|
|
var resource_name = get_resource_desc(resource_unique_name)['Name'];
|
|
show_resource = show_resource || resource_name.toLowerCase().includes(search_resource.toLowerCase());
|
|
}
|
|
}
|
|
|
|
var has_input_or_outputs = pass_data['InputResources'].length > 0 || pass_data['OutputResources'].length > 0;
|
|
|
|
if (show_pass && show_resource && has_input_or_outputs)
|
|
{
|
|
var shared_scope = 0;
|
|
for (var i = 0; i < Math.min(parent_event_scopes.length, pass_event_scopes.length); i++)
|
|
{
|
|
if (parent_event_scopes[i] == pass_event_scopes[pass_event_scopes.length - 1 - i])
|
|
{
|
|
shared_scope++;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
parent_event_scopes = parent_event_scopes.slice(0, shared_scope);
|
|
|
|
for (var i = shared_scope; i < pass_event_scopes.length; i++)
|
|
{
|
|
var scope = pass_event_scopes[pass_event_scopes.length - 1 - i];
|
|
html += `<a style="padding-left: ${10 + 16 * i}px;" class="disabled">${scope}</a>`;
|
|
parent_event_scopes.push(scope);
|
|
}
|
|
|
|
html += `<a style="padding-left: ${10 + 16 * pass_event_scopes.length}px;" href="#display_pass(${pass_id});">${pass_data['EventName']}</a>`;
|
|
}
|
|
});
|
|
document.getElementById('pass_hierarchy').innerHTML = html;
|
|
update_href_selection(document.getElementById('pass_hierarchy'));
|
|
}
|
|
|
|
class ResourceView extends IView
|
|
{
|
|
constructor(subresource_version_info, resource_desc)
|
|
{
|
|
super();
|
|
this.subresource_version_info = subresource_version_info;
|
|
this.resource_desc = resource_desc;
|
|
this.onload = function() { };
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [];
|
|
}
|
|
}
|
|
|
|
class PassView extends IView
|
|
{
|
|
constructor(pass_id)
|
|
{
|
|
super();
|
|
this.pass_id = pass_id;
|
|
this.pass_data = g_passes[pass_id];
|
|
this.pass_draws_data = [];
|
|
this.resource_view = null;
|
|
|
|
if (this.pass_data['DrawCount'] > 0)
|
|
{
|
|
this.pass_draws_data = load_json_dict_sequence(`Passes/Pass.${this.pass_data['Pointer']}.Draws.json`);
|
|
}
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
var column_width = '50%';
|
|
var draw_column_display = 'none';
|
|
|
|
if (this.pass_draws_data.length > 0)
|
|
{
|
|
column_width = '33%';
|
|
draw_column_display = 'block';
|
|
}
|
|
|
|
parent_dom.innerHTML = `
|
|
<div class="pass_title main_div">
|
|
<div id="pass_frame_list" style="display:none;"></div>
|
|
${this.pass_data['EventName']}
|
|
<div class="button_list" style="display:inline-block;"><a href="#display_pass_parameters(${this.pass_id});">PassParameters</a></div>
|
|
</div>
|
|
<table width="100%">
|
|
<tr>
|
|
<td width="${column_width}">
|
|
<div class="main_div">
|
|
<div class="selection_list_title">Input resources</div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="pass_input_resource_search" oninput="g_view.refresh_input_resource_list();" onchange="g_view.refresh_input_resource_list();" placeholder="Search input resource..." />
|
|
</div>
|
|
<div class="selection_list" id="pass_input_resource_list"></div>
|
|
</div>
|
|
</td>
|
|
<td width="${column_width}">
|
|
<div class="main_div">
|
|
<div class="selection_list_title">Output resources</div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="pass_output_resource_search" oninput="g_view.refresh_output_resource_list();" onchange="g_view.refresh_output_resource_list();" placeholder="Search output resource..." />
|
|
</div>
|
|
<div class="selection_list" id="pass_output_resource_list"></div>
|
|
</div>
|
|
</td>
|
|
<td width="${column_width}">
|
|
<div class="main_div" style="display: ${draw_column_display};">
|
|
<div class="selection_list_title">Draws</div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="pass_draw_search" oninput="g_view.refresh_draw_resource_list();" onchange="g_view.refresh_draw_resource_list();" placeholder="Search draw..." />
|
|
</div>
|
|
<div class="selection_list" id="pass_draw_list"></div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div id="display_resource_pannel"></div>
|
|
<table width="100%">
|
|
<tr>
|
|
<td width="50%">
|
|
<div class="main_div" id="resource_pass_modifying_outter">
|
|
<div class="selection_list_title" id="resource_pass_modifying_title"></div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="resource_pass_modifying_search" oninput="g_view.refresh_resource_modifying_list();" onchange="g_view.refresh_resource_modifying_list();" placeholder="Search modifying pass..." />
|
|
</div>
|
|
<div class="selection_list" id="resource_pass_modifying_list"></div>
|
|
</div>
|
|
</td>
|
|
<td width="50%">
|
|
<div class="main_div" id="resource_pass_reading_outter">
|
|
<div class="selection_list_title" id="resource_pass_reading_title"></div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="resource_pass_reading_search" oninput="g_view.refresh_resource_reading_list();" onchange="g_view.refresh_resource_reading_list();" placeholder="Search reading pass..." />
|
|
</div>
|
|
<div class="selection_list" id="resource_pass_reading_list"></div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
`;
|
|
}
|
|
|
|
refresh_all_lists()
|
|
{
|
|
this.refresh_frames_lists();
|
|
this.refresh_input_resource_list();
|
|
this.refresh_output_resource_list();
|
|
this.refresh_draw_resource_list();
|
|
this.refresh_resource_modifying_list();
|
|
this.refresh_resource_reading_list();
|
|
}
|
|
|
|
refresh_frames_lists()
|
|
{
|
|
if (g_analysis['FrameList'].length == 1)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.resource_view === null)
|
|
{
|
|
document.getElementById('pass_frame_list').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
var pass_data = this.pass_data;
|
|
var subresource_version_info = this.resource_view.subresource_version_info;
|
|
|
|
var current_frame_number = get_frame_number(this.pass_data);
|
|
var prev_frame_number = g_analysis['FrameList'].includes(current_frame_number - 1) ? current_frame_number - 1 : g_analysis['FrameList'][g_analysis['FrameList'].length - 1];
|
|
var next_frame_number = g_analysis['FrameList'].includes(current_frame_number + 1) ? current_frame_number + 1 : g_analysis['FrameList'][0];
|
|
|
|
var prev_frame_pass_id = find_similar_pass_id_in_frame(this.pass_data, prev_frame_number);
|
|
var next_frame_pass_id = find_similar_pass_id_in_frame(this.pass_data, next_frame_number);
|
|
|
|
if (prev_frame_pass_id == null || next_frame_pass_id == null)
|
|
{
|
|
document.getElementById('pass_frame_list').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
var prev_frame_href = '';
|
|
var next_frame_href = '';
|
|
if (this.resource_view.resource_desc['Desc'] == 'FRDGParameterStruct')
|
|
{
|
|
prev_frame_href = `#display_pass_parameters(${prev_frame_pass_id});`;
|
|
next_frame_href = `#display_pass_parameters(${next_frame_pass_id});`;
|
|
}
|
|
else
|
|
{
|
|
var prev_frame_pass_data = g_passes[prev_frame_pass_id];
|
|
var next_frame_pass_data = g_passes[next_frame_pass_id];
|
|
|
|
var is_output_resource = is_pass_output_resource(this.pass_data, this.resource_view.subresource_version_info);
|
|
var prev_frame_subresource_info = find_similar_resource_in_pass(this.pass_data, this.resource_view.resource_desc, this.resource_view.subresource_version_info, prev_frame_pass_data);
|
|
var next_frame_subresource_info = find_similar_resource_in_pass(this.pass_data, this.resource_view.resource_desc, this.resource_view.subresource_version_info, next_frame_pass_data);
|
|
|
|
var prev_frame_href = '';
|
|
var next_frame_href = '';
|
|
if (prev_frame_subresource_info == null || next_frame_subresource_info == null)
|
|
{
|
|
document.getElementById('pass_frame_list').style.display = 'none';
|
|
return;
|
|
}
|
|
else if (is_output_resource)
|
|
{
|
|
prev_frame_href = `#display_output_resource(${prev_frame_pass_id},'${get_subresource_unique_name(prev_frame_subresource_info)}');`;
|
|
next_frame_href = `#display_output_resource(${next_frame_pass_id},'${get_subresource_unique_name(next_frame_subresource_info)}');`;
|
|
}
|
|
else
|
|
{
|
|
prev_frame_href = `#display_input_resource(${prev_frame_pass_id},'${get_subresource_unique_name(prev_frame_subresource_info)}');`;
|
|
next_frame_href = `#display_input_resource(${next_frame_pass_id},'${get_subresource_unique_name(next_frame_subresource_info)}');`;
|
|
}
|
|
}
|
|
|
|
document.getElementById('pass_frame_list').style.display = 'inline-block';
|
|
document.getElementById('pass_frame_list').innerHTML = `
|
|
<div class="button_list" style="display:inline-block;"><a href="${prev_frame_href}">-</a><a title="Id of the current frame">${current_frame_number}</a><a href="${next_frame_href}">+</a></div>
|
|
`;
|
|
}
|
|
|
|
refresh_input_resource_list()
|
|
{
|
|
var display_list = [];
|
|
for (const subresource_unique_name of this.pass_data['InputResources'])
|
|
{
|
|
var subresource_info = parse_subresource_unique_name(subresource_unique_name);
|
|
var resource_desc = get_resource_desc(subresource_info['resource']);
|
|
|
|
var name = prettify_subresource_unique_name(subresource_info, resource_desc);
|
|
var href = null;
|
|
if (resource_desc)
|
|
{
|
|
href = `#display_input_resource(${this.pass_id},'${subresource_unique_name}');`
|
|
}
|
|
|
|
display_list.push({'name': name, 'href': href});
|
|
}
|
|
|
|
render_selection_list_html(
|
|
document.getElementById('pass_input_resource_list'),
|
|
display_list,
|
|
{
|
|
'search': document.getElementById('pass_input_resource_search').value,
|
|
'deduplicate': true,
|
|
'sort': true
|
|
});
|
|
}
|
|
|
|
refresh_output_resource_list()
|
|
{
|
|
var draw_id = -1;
|
|
if (this.resource_view)
|
|
{
|
|
draw_id = this.resource_view.subresource_version_info['draw'];
|
|
}
|
|
|
|
var display_list = [];
|
|
for (const subresource_unique_name of this.pass_data['OutputResources'])
|
|
{
|
|
var subresource_info = parse_subresource_unique_name(subresource_unique_name);
|
|
var resource_desc = get_resource_desc(subresource_info['resource']);
|
|
|
|
var name = prettify_subresource_unique_name(subresource_info, resource_desc);
|
|
var href = null;
|
|
if (resource_desc)
|
|
{
|
|
if (draw_id >= 0)
|
|
{
|
|
href = `#display_draw_output_resource(${this.pass_id},'${subresource_unique_name}',${draw_id});`;
|
|
}
|
|
else
|
|
{
|
|
href = `#display_output_resource(${this.pass_id},'${subresource_unique_name}');`;
|
|
}
|
|
}
|
|
|
|
display_list.push({'name': name, 'href': href});
|
|
}
|
|
|
|
render_selection_list_html(
|
|
document.getElementById('pass_output_resource_list'),
|
|
display_list,
|
|
{
|
|
'search': document.getElementById('pass_output_resource_search').value,
|
|
});
|
|
}
|
|
|
|
refresh_draw_resource_list()
|
|
{
|
|
var is_output_resource = false;
|
|
var subresource_unique_name = '';
|
|
if (this.resource_view)
|
|
{
|
|
is_output_resource = is_pass_output_resource(this.pass_data, this.resource_view.subresource_version_info);
|
|
subresource_unique_name = get_subresource_unique_name(this.resource_view.subresource_version_info);
|
|
}
|
|
|
|
var display_list = [];
|
|
for (var draw_id = 0; draw_id < this.pass_draws_data.length; draw_id++)
|
|
{
|
|
var href = null;
|
|
if (subresource_unique_name && is_output_resource)
|
|
{
|
|
href = `#display_draw_output_resource(${this.pass_id},'${subresource_unique_name}',${draw_id});`;
|
|
}
|
|
|
|
display_list.push({
|
|
'name': `${draw_id}: ${this.pass_draws_data[draw_id]['DrawName']}`,
|
|
'href': href
|
|
});
|
|
}
|
|
|
|
if (subresource_unique_name)
|
|
{
|
|
var href = null;
|
|
if (is_output_resource)
|
|
{
|
|
href = `#display_output_resource(${this.pass_id},'${subresource_unique_name}');`;
|
|
}
|
|
|
|
display_list.push({
|
|
'name': 'Final pass output',
|
|
'href': href
|
|
});
|
|
}
|
|
|
|
render_selection_list_html(
|
|
document.getElementById('pass_draw_list'),
|
|
display_list,
|
|
{
|
|
'search': document.getElementById('pass_draw_search').value,
|
|
});
|
|
}
|
|
|
|
refresh_resource_modifying_list()
|
|
{
|
|
if (this.resource_view === null || this.resource_view.resource_desc['Desc'] == 'FRDGParameterStruct')
|
|
{
|
|
document.getElementById('resource_pass_modifying_outter').style.display = 'none';
|
|
return;
|
|
}
|
|
document.getElementById('resource_pass_modifying_outter').style.display = 'block';
|
|
|
|
var subresource_unique_name = get_subresource_unique_name(this.resource_view.subresource_version_info);
|
|
var resource_desc = get_resource_desc(this.resource_view.subresource_version_info['resource']);
|
|
|
|
var display_list = [];
|
|
for (var pass_id = 0; pass_id < g_passes.length; pass_id++)
|
|
{
|
|
var producer = false;
|
|
g_passes[pass_id]['OutputResources'].forEach(function(value) {
|
|
if (value == subresource_unique_name)
|
|
{
|
|
producer = true;
|
|
}
|
|
});
|
|
|
|
if (producer)
|
|
{
|
|
display_list.push({
|
|
'name': g_passes[pass_id]['EventName'],
|
|
'href': `#display_output_resource(${pass_id},'${subresource_unique_name}');`,
|
|
});
|
|
}
|
|
}
|
|
|
|
document.getElementById('resource_pass_modifying_title').innerHTML = `Passes modifying ${resource_desc['Name']}`;
|
|
render_selection_list_html(
|
|
document.getElementById('resource_pass_modifying_list'),
|
|
display_list,
|
|
{
|
|
'search': document.getElementById('resource_pass_modifying_search').value,
|
|
});
|
|
}
|
|
|
|
refresh_resource_reading_list()
|
|
{
|
|
if (this.resource_view === null || this.resource_view.resource_desc['Desc'] == 'FRDGParameterStruct')
|
|
{
|
|
document.getElementById('resource_pass_reading_outter').style.display = 'none';
|
|
return;
|
|
}
|
|
document.getElementById('resource_pass_reading_outter').style.display = 'block';
|
|
|
|
var subresource_unique_name = get_subresource_unique_name(this.resource_view.subresource_version_info);
|
|
var resource_desc = get_resource_desc(this.resource_view.subresource_version_info['resource']);
|
|
|
|
var display_list = [];
|
|
for (var pass_id = 0; pass_id < g_passes.length; pass_id++)
|
|
{
|
|
var reader = false;
|
|
g_passes[pass_id]['InputResources'].forEach(function(value) {
|
|
if (value == subresource_unique_name)
|
|
{
|
|
reader = true;
|
|
}
|
|
});
|
|
|
|
if (reader)
|
|
{
|
|
display_list.push({
|
|
'name': g_passes[pass_id]['EventName'],
|
|
'href': `#display_input_resource(${pass_id},'${subresource_unique_name}');`,
|
|
});
|
|
}
|
|
}
|
|
|
|
document.getElementById('resource_pass_reading_title').innerHTML = `Passes reading ${resource_desc['Name']}`;
|
|
render_selection_list_html(
|
|
document.getElementById('resource_pass_reading_list'),
|
|
display_list,
|
|
{
|
|
'search': document.getElementById('resource_pass_reading_search').value,
|
|
});
|
|
}
|
|
|
|
resize(ctx)
|
|
{
|
|
if (this.resource_view !== null)
|
|
{
|
|
this.resource_view.resize(ctx);
|
|
}
|
|
}
|
|
|
|
set_resource_view(new_resource_view)
|
|
{
|
|
var parent_dom = document.getElementById('display_resource_pannel');
|
|
if (this.resource_view !== null)
|
|
{
|
|
this.resource_view.release();
|
|
delete this.resource_view;
|
|
parent_dom.innerHTML = '';
|
|
}
|
|
|
|
this.resource_view = new_resource_view;
|
|
if (this.resource_view !== null)
|
|
{
|
|
this.resource_view.setup_html(parent_dom);
|
|
this.refresh_all_lists();
|
|
onresize_body();
|
|
}
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
var navs = [`display_pass(${this.pass_id});`];
|
|
|
|
if (this.resource_view !== null)
|
|
{
|
|
navs.concat(this.resource_view.navigations);
|
|
}
|
|
|
|
return navs;
|
|
}
|
|
|
|
release()
|
|
{
|
|
this.set_resource_view(null);
|
|
}
|
|
}
|
|
|
|
function display_pass_internal(pass_id)
|
|
{
|
|
if (g_view instanceof PassView && pass_id == g_view.pass_id)
|
|
{
|
|
return;
|
|
}
|
|
|
|
set_main_view(new PassView(pass_id));
|
|
}
|
|
|
|
function display_resource_internal(subresource_version_info)
|
|
{
|
|
var resource_desc = get_resource_desc(subresource_version_info['resource']);
|
|
if (!resource_desc)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (g_view.resource_view !== null && get_subresource_unique_version_name(g_view.resource_view.subresource_version_info) == get_subresource_unique_version_name(subresource_version_info))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (resource_desc['Desc'] == 'FRDGBufferDesc')
|
|
{
|
|
if (resource_desc['Usage'].includes('AccelerationStructure'))
|
|
{
|
|
g_view.set_resource_view(new RaytracingAccelerationStructureView(subresource_version_info, resource_desc));
|
|
}
|
|
else
|
|
{
|
|
g_view.set_resource_view(new BufferView(subresource_version_info, resource_desc));
|
|
}
|
|
}
|
|
else if (resource_desc['Desc'] == 'FRDGTextureDesc' && (resource_desc['Type'] == 'Texture2D' || resource_desc['Type'] == 'Texture2DArray'))
|
|
{
|
|
var new_texture_view = new TextureView(subresource_version_info, resource_desc);
|
|
|
|
// Keep the same zoom settings if the subresources are exactly the same extent
|
|
{
|
|
var zoom_settings = null;
|
|
|
|
if (!zoom_settings && g_view && 'resource_view' in g_view && g_view.resource_view instanceof TextureView)
|
|
{
|
|
var prev_zoom_settings = g_view.resource_view.get_zoom_settings();
|
|
if (new_texture_view.is_zoom_settings_compatible(prev_zoom_settings))
|
|
{
|
|
zoom_settings = prev_zoom_settings;
|
|
}
|
|
}
|
|
|
|
if (!zoom_settings)
|
|
{
|
|
var global_zoom_settings = get_local_storage(k_texture_zoom_local_storage);
|
|
if (global_zoom_settings && new_texture_view.is_zoom_settings_compatible(global_zoom_settings))
|
|
{
|
|
zoom_settings = global_zoom_settings;
|
|
}
|
|
}
|
|
|
|
if (zoom_settings)
|
|
{
|
|
new_texture_view.onload = function() {
|
|
new_texture_view.apply_zoom_settings(zoom_settings);
|
|
};
|
|
}
|
|
}
|
|
|
|
g_view.set_resource_view(new_texture_view);
|
|
}
|
|
else
|
|
{
|
|
add_console_event('error', `Unable to display ${resource_desc['Name']}: ${resource_desc['Desc']} of type ${resource_desc['Type']} is not supported`);
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- HREF FUNCTIONS
|
|
|
|
function display_pass(pass_id)
|
|
{
|
|
// Load first resource
|
|
if (g_passes[pass_id]['OutputResources'][0])
|
|
{
|
|
redirect_to_hash(`display_output_resource(${pass_id},'${g_passes[pass_id]['OutputResources'][0]}');`);
|
|
}
|
|
else if (g_passes[pass_id]['InputResources'][0])
|
|
{
|
|
redirect_to_hash(`display_input_resource(${pass_id},'${g_passes[pass_id]['InputResources'][0]}');`);
|
|
}
|
|
else
|
|
{
|
|
display_pass_internal(pass_id);
|
|
}
|
|
}
|
|
|
|
function display_input_resource(pass_id, subresource_unique_name)
|
|
{
|
|
var previous_producer_pass = -1;
|
|
for (var i = 0; i < pass_id; i++)
|
|
{
|
|
var cur_outputs = g_passes[i]['OutputResources'];
|
|
|
|
cur_outputs.forEach(function(value) {
|
|
if (value == subresource_unique_name)
|
|
{
|
|
previous_producer_pass = i;
|
|
}
|
|
});
|
|
}
|
|
|
|
var subresource_version_info = parse_subresource_unique_name(subresource_unique_name);
|
|
if (previous_producer_pass >= 0)
|
|
{
|
|
subresource_version_info['pass'] = g_passes[previous_producer_pass]['Pointer'];
|
|
}
|
|
else
|
|
{
|
|
subresource_version_info['pass'] = k_null_json_ptr;
|
|
}
|
|
|
|
display_pass_internal(pass_id);
|
|
display_resource_internal(subresource_version_info);
|
|
}
|
|
|
|
function display_output_resource(pass_id, subresource_unique_name)
|
|
{
|
|
var subresource_version_info = parse_subresource_unique_name(subresource_unique_name);
|
|
subresource_version_info['pass'] = g_passes[pass_id]['Pointer'];
|
|
|
|
display_pass_internal(pass_id);
|
|
display_resource_internal(subresource_version_info);
|
|
}
|
|
|
|
function display_draw_output_resource(pass_id, subresource_unique_name, draw_id)
|
|
{
|
|
var subresource_version_info = parse_subresource_unique_name(subresource_unique_name);
|
|
subresource_version_info['pass'] = g_passes[pass_id]['Pointer'];
|
|
subresource_version_info['draw'] = draw_id;
|
|
|
|
display_pass_internal(pass_id);
|
|
display_resource_internal(subresource_version_info);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY VIEWER CONSOLE
|
|
|
|
var g_console_events = [];
|
|
var g_console_error_count = 0;
|
|
|
|
class ConsoleEvent
|
|
{
|
|
constructor(type, message)
|
|
{
|
|
this.type = type;
|
|
this.message = message;
|
|
}
|
|
}
|
|
|
|
class ConsoleView extends IView
|
|
{
|
|
constructor()
|
|
{
|
|
super();
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
parent_dom.innerHTML = `
|
|
<div class="main_div">
|
|
<div class="pass_title">Viewer Console</div>
|
|
<div id="console_events_pannel"></div>
|
|
</div>`;
|
|
|
|
document.title = 'Viewer Console';
|
|
this.update_console_events();
|
|
}
|
|
|
|
update_console_events()
|
|
{
|
|
var html = `
|
|
<table width="100%" class="pretty_table">`;
|
|
|
|
for (var console_event of g_console_events)
|
|
{
|
|
html += `
|
|
<tr class="${console_event.type}">
|
|
<td>${console_event.type}: ${console_event.message}</td>
|
|
</tr>`;
|
|
};
|
|
|
|
html += `
|
|
</table>`;
|
|
|
|
document.getElementById('console_events_pannel').innerHTML = html;
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [`display_console();`];
|
|
}
|
|
}
|
|
|
|
function update_console_button()
|
|
{
|
|
if (document.getElementById('console_button') && g_console_error_count > 0)
|
|
{
|
|
document.getElementById('console_button').classList.add('error');
|
|
document.getElementById('console_button').innerHTML = `Console (${g_console_error_count} Errors)`;
|
|
}
|
|
}
|
|
|
|
function add_console_event(type, message)
|
|
{
|
|
console.assert(['error', 'log'].includes(type));
|
|
g_console_events.push(new ConsoleEvent(type, message));
|
|
|
|
if (type == 'error')
|
|
{
|
|
g_console_error_count += 1;
|
|
update_console_button();
|
|
}
|
|
|
|
if (g_view instanceof ConsoleView)
|
|
{
|
|
g_view.update_console_events();
|
|
}
|
|
}
|
|
|
|
function display_console(tip_id)
|
|
{
|
|
set_main_view(new ConsoleView());
|
|
}
|
|
|
|
function init_console()
|
|
{
|
|
window.addEventListener('error', function(event) {
|
|
add_console_event('error', `${event.filename}:${event.fileno}: ${event.message}`);
|
|
});
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY INFOS
|
|
|
|
class InfosView extends IView
|
|
{
|
|
constructor()
|
|
{
|
|
super();
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
var info_htmls = '';
|
|
{
|
|
info_htmls += `
|
|
<table width="100%" class="pretty_table">`;
|
|
|
|
for (var key in g_infos)
|
|
{
|
|
if (g_infos.hasOwnProperty(key))
|
|
{
|
|
info_htmls += `
|
|
<tr>
|
|
<td width="100px">${key}</td>
|
|
<td>${g_infos[key]}</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
|
|
info_htmls += `
|
|
</table>`;
|
|
}
|
|
|
|
parent_dom.innerHTML = `
|
|
<div class="main_div">
|
|
<div class="pass_title">Infos</div>
|
|
<div id="infos_pannel">${info_htmls}</div>
|
|
</div>`;
|
|
|
|
if (does_file_exists('Base/Screenshot.png'))
|
|
{
|
|
parent_dom.innerHTML += `
|
|
<div class="main_div">
|
|
<div class="pass_title">Screenshot</div>
|
|
<img src="${k_current_dir}/Base/Screenshot.png" style="width: 100%;" />
|
|
</div>`;
|
|
}
|
|
|
|
document.title = 'Dump infos';
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [`display_infos();`];
|
|
}
|
|
}
|
|
|
|
function display_infos(tip_id)
|
|
{
|
|
set_main_view(new InfosView());
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY TIP
|
|
|
|
const k_tips = [
|
|
// Dumping process
|
|
'Can use the CTRL+SHIFT+/ keyboard shortcut to summon the DumpGPU command.',
|
|
'Speed up your frame dump by only selecting the passes you need with r.DumpGPU.Root. For instance r.DumpGPU.Root="*PostProcessing*".',
|
|
'Uses r.DumpGPU.Delay to delay the dump of few seconds to have time to repro the issue with gameplay logic (for instance moving arround in the map).',
|
|
'Uses r.DumpGPU.FrameCount to dump more than one frame. This is useful when artifact may be produced only some frames but end up stucked in temporal histories.',
|
|
'Uses r.DumpGPU.FixedTickRate to automatically override the engine\'s tick rate to fixed value when dumping multiple frames.',
|
|
'Uses r.DumpGPU.DumpOnScreenshotTest=1 to automatically produce a GPU dump of the frame producing screenshot in the automated AScreenshotFunctionalTest',
|
|
'GPU dumps can be large and accumulate on your hard drive in your various projects\' Saved/ directories. Set r.DumpGPU.Directory="D:/tmp/DumpGPU/" in your console variables or UE-DumpGPUPath environment variable to dump them all at the same location on your machine.',
|
|
'Uses -cvarsini to override console variables with your own ConsoleVariables.ini file on a cooked build.',
|
|
|
|
// General navigations
|
|
'All navigation links can be open in a new tab.',
|
|
'Uses the browser\'s back button to navigate to previously inspected resource.',
|
|
'Make sure to browse the dump informations.',
|
|
'Make sure to browse the console variables.',
|
|
'Make sure to browse the log file.',
|
|
'Share the part of the URL after the # (for instance #display_input_resource(96,\'TSR.AntiAliasing.Noise.000000006b9b2c00.mip0\');) to anyone else whom have this GPU dump to so they too can navigate to the exact same resource view.',
|
|
'Set r.DumpGPU.Viewer.Visualize in your ConsoleVariables.ini so the dump viewer automatically open this RDG output resource at startup.',
|
|
|
|
// Buffer visualization
|
|
'Uses the templated FRDGBufferDesc::Create*<FMyStructure>(NumElements) to display your buffer more conveniently with FMyStructure layout in the buffer visualization.',
|
|
'Buffer visualization supports float, half, int, uint, short, ushort, char, uchar, as well as hexadecimal with hex(uint) and binary with bin(uint).',
|
|
'Uses the "Address..." field at the very left of the buffer view\'s header to navigate to a specific address. Supports decimal and 0x prefixed hexadecimal notations.',
|
|
|
|
// Texture visualization
|
|
'Click the texture viewer to then zoom in and out with your mouse wheel.',
|
|
'Right click to drag texture viewport arround when zoomed in.',
|
|
'Uses the texture viewer\'s "copy to clipboard" to share your texture visualization to anyone.',
|
|
];
|
|
|
|
class TipView extends IView
|
|
{
|
|
constructor(tip_id)
|
|
{
|
|
super();
|
|
this.tip_id = tip_id;
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
parent_dom.innerHTML = `
|
|
<div style="padding-top: 20%; width: 50%; margin: 0 auto; font-size: 14;">
|
|
<div>User tip:</div>
|
|
<div style="padding: 20px;">${k_tips[this.tip_id]}</div>
|
|
<div class="button_list" style="margin-left: auto; display: block; width: 100px;">
|
|
<a href="#display_tip(${(this.tip_id + 1) % k_tips.length});">Next</a>
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [`display_tip(${this.tip_id});`];
|
|
}
|
|
}
|
|
|
|
function display_tip(tip_id)
|
|
{
|
|
if (tip_id === undefined)
|
|
{
|
|
tip_id = Math.floor(Math.random() * k_tips.length);
|
|
document.title = 'GPU Dump Viewer';
|
|
}
|
|
else
|
|
{
|
|
document.title = `User tip #${tip_id + 1}`;
|
|
}
|
|
|
|
set_main_view(new TipView(tip_id));
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY CONSOLE VARIABLES
|
|
|
|
class CVarsView extends IView
|
|
{
|
|
constructor()
|
|
{
|
|
super();
|
|
this.load_cvars();
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
parent_dom.innerHTML = `
|
|
<div class="main_div">
|
|
<div class="pass_title">Console variables</div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="cvars_search_input" oninput="g_view.refresh_cvars();" onchange="g_view.refresh_cvars();" style="width: 100%;" placeholder="Search console variables..." />
|
|
</div>
|
|
<div id="cvars_pannel"></div>
|
|
</div>`;
|
|
|
|
this.refresh_cvars();
|
|
document.title = 'Console variables';
|
|
}
|
|
|
|
load_cvars()
|
|
{
|
|
var cvars_csv = load_text_file('Base/ConsoleVariables.csv');
|
|
|
|
this.cvars = [];
|
|
var cvars_set = new Set();
|
|
var csv_lines = cvars_csv.split('\n');
|
|
for (var i = 1; i < csv_lines.length - 1; i++)
|
|
{
|
|
var csv_line = csv_lines[i].split(',');
|
|
var entry = {};
|
|
entry['name'] = csv_line[0];
|
|
entry['type'] = csv_line[1];
|
|
entry['set_by'] = csv_line[2];
|
|
entry['value'] = csv_line[3];
|
|
|
|
if (!cvars_set.has(entry['name']))
|
|
{
|
|
cvars_set.add(entry['name']);
|
|
this.cvars.push(entry);
|
|
}
|
|
}
|
|
|
|
this.cvars.sort(function(a, b)
|
|
{
|
|
if (a['name'] < b['name'])
|
|
{
|
|
return -1;
|
|
}
|
|
else if (a['name'] > b['name'])
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
refresh_cvars()
|
|
{
|
|
var html = `
|
|
<table width="100%" class="pretty_table">
|
|
<tr class="header">
|
|
<td>Name</td>
|
|
<td width="15%">Value</td>
|
|
<td width="30px">Type</td>
|
|
<td width="100px">Set by</td>
|
|
</tr>`;
|
|
|
|
var cvar_search = document.getElementById('cvars_search_input').value.toLowerCase();
|
|
|
|
this.cvars.forEach(function(cvar)
|
|
{
|
|
var display = true;
|
|
if (cvar_search)
|
|
{
|
|
display = cvar['name'].toLowerCase().includes(cvar_search);
|
|
}
|
|
|
|
if (display)
|
|
{
|
|
html += `
|
|
<tr>
|
|
<td>${cvar['name']}</td>
|
|
<td>${cvar['value']}</td>
|
|
<td>${cvar['type']}</td>
|
|
<td>${cvar['set_by']}</td>
|
|
</tr>`;
|
|
}
|
|
});
|
|
|
|
html += `
|
|
</table>`;
|
|
|
|
document.getElementById('cvars_pannel').innerHTML = html;
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [`display_cvars();`];
|
|
}
|
|
|
|
release()
|
|
{
|
|
this.cvars = null;
|
|
}
|
|
}
|
|
|
|
function display_cvars()
|
|
{
|
|
set_main_view(new CVarsView());
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY LOG
|
|
|
|
function get_log_path()
|
|
{
|
|
return `Base/${g_infos['LogFilename']}`;
|
|
}
|
|
|
|
class LogView extends IView
|
|
{
|
|
constructor()
|
|
{
|
|
super();
|
|
|
|
this.log = [];
|
|
|
|
var log = load_text_file(get_log_path());
|
|
if (log)
|
|
{
|
|
this.log = log.split('\n');
|
|
}
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
parent_dom.innerHTML = `
|
|
<div class="main_div">
|
|
<div class="pass_title">${g_infos['LogFilename']}</div>
|
|
<div class="selection_list_search">
|
|
<input type="search" id="log_search_input" oninput="g_view.refresh_log();" onchange="g_view.refresh_log();" style="width: 100%;" placeholder="Search log..." />
|
|
</div>
|
|
<div id="log_pannel"></div>
|
|
</div>`;
|
|
|
|
this.refresh_log();
|
|
document.title = 'Log';
|
|
}
|
|
|
|
refresh_log()
|
|
{
|
|
var html = `
|
|
<table width="100%" class="pretty_table">`;
|
|
|
|
var log_search = document.getElementById('log_search_input').value.toLowerCase();
|
|
|
|
this.log.forEach(function(log_line)
|
|
{
|
|
var display = true;
|
|
if (log_search)
|
|
{
|
|
display = log_line.toLowerCase().includes(log_search);
|
|
}
|
|
|
|
if (display)
|
|
{
|
|
html += `
|
|
<tr>
|
|
<td>${log_line}</td>
|
|
</tr>`;
|
|
}
|
|
});
|
|
|
|
html += `
|
|
</table>`;
|
|
|
|
document.getElementById('log_pannel').innerHTML = html;
|
|
}
|
|
|
|
get navigations()
|
|
{
|
|
return [`display_log();`];
|
|
}
|
|
|
|
resize(ctx)
|
|
{
|
|
document.getElementById('log_pannel').style.width = `${ctx.width - 2 * k_style_padding_size}px`;
|
|
document.getElementById('log_pannel').style.height = `${ctx.height - 2 * k_style_padding_size - 75}px`;
|
|
document.getElementById('log_pannel').style.overflow = `scroll`;
|
|
}
|
|
|
|
release()
|
|
{
|
|
this.log = null;
|
|
}
|
|
}
|
|
|
|
function display_log()
|
|
{
|
|
set_main_view(new LogView());
|
|
}
|
|
|
|
|
|
|
|
// -------------------------------------------------------------------- WEBGL UTILS
|
|
|
|
function verify_backbuffer_color_space(gl)
|
|
{
|
|
// https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/drawingBufferColorSpace
|
|
const desired_color_space = 'srgb';
|
|
if (gl.drawingBufferColorSpace !== desired_color_space)
|
|
{
|
|
add_console_event('error', `WebGL backbuffer's color space isn't ${desired_color_space}, it is ${gl.drawingBufferColorSpace}`);
|
|
}
|
|
}
|
|
|
|
function copy_canvas_to_clipboard(canvas, text)
|
|
{
|
|
canvas.toBlob(function(image_blob) {
|
|
var text_blob = new Blob([text], { type: 'text/plain' });
|
|
var clipboard_data = {
|
|
[text_blob.type]: text_blob,
|
|
[image_blob.type]: image_blob,
|
|
};
|
|
|
|
navigator.clipboard.write([new ClipboardItem(clipboard_data)]);
|
|
}, 'image/png');
|
|
}
|
|
|
|
function gl_create_vertex_buffer(gl, vertices)
|
|
{
|
|
var webgl_vertex_buffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, webgl_vertex_buffer);
|
|
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
|
return webgl_vertex_buffer;
|
|
}
|
|
|
|
function gl_create_index_buffer(gl, indices)
|
|
{
|
|
var webgl_index_buffer = gl.createBuffer();
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webgl_index_buffer);
|
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indices, gl.STATIC_DRAW);
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
|
|
return webgl_index_buffer;
|
|
}
|
|
|
|
function gl_create_shader(gl, shader_type, shader_code)
|
|
{
|
|
const shader_std = `#version 300 es
|
|
|
|
precision highp float;
|
|
precision highp int;
|
|
precision highp sampler2D;
|
|
precision highp usampler2D;
|
|
|
|
struct FloatInfos
|
|
{
|
|
bool is_denormal;
|
|
bool is_infinity;
|
|
bool is_nan;
|
|
};
|
|
|
|
FloatInfos decodeFloat(float x)
|
|
{
|
|
const uint mantissa_bit_count = 23u;
|
|
const uint exp_bit_count = 8u;
|
|
|
|
uint raw = floatBitsToUint(x);
|
|
|
|
uint sign_bit = (raw >> (mantissa_bit_count + exp_bit_count)) & 0x1u;
|
|
uint mantissa_bits = (raw >> 0u) & ((0x1u << mantissa_bit_count) - 1u);
|
|
uint exp_bits = (raw >> mantissa_bit_count) & ((0x1u << exp_bit_count) - 1u);
|
|
|
|
bool is_max_exp = exp_bits == ((0x1u << exp_bit_count) - 1u);
|
|
|
|
FloatInfos infos;
|
|
infos.is_denormal = exp_bits == 0u;
|
|
infos.is_infinity = is_max_exp && mantissa_bits == 0u;
|
|
infos.is_nan = is_max_exp && mantissa_bits != 0u;
|
|
|
|
return infos;
|
|
}
|
|
|
|
#define isnan(x) decodeFloat(x).is_nan
|
|
#define isinf(x) decodeFloat(x).is_infinity
|
|
#define isdenorm(x) decodeFloat(x).is_denormal
|
|
|
|
// https://www.shadertoy.com/view/7tKXWR
|
|
vec3 LinearToWebGLDisplayGamma(vec3 Color)
|
|
{
|
|
return pow(Color, vec3(1.0/2.2));
|
|
}
|
|
|
|
vec3 LinearToRec709(vec3 lin)
|
|
{
|
|
return min(lin * vec3(4.5), pow(max(lin, vec3(0.018)), vec3(0.45)) * vec3(1.099) - vec3(0.099));
|
|
}
|
|
|
|
vec3 Rec709ToLinear(vec3 Color)
|
|
{
|
|
Color.r = Color.r > 0.081 ? pow((Color.r + 0.099) / 1.099, (1.0 / 0.45)) : Color.r / 4.5;
|
|
Color.g = Color.g > 0.081 ? pow((Color.g + 0.099) / 1.099, (1.0 / 0.45)) : Color.g / 4.5;
|
|
Color.b = Color.b > 0.081 ? pow((Color.b + 0.099) / 1.099, (1.0 / 0.45)) : Color.b / 4.5;
|
|
return Color;
|
|
}
|
|
|
|
vec3 LinearToSRGB(vec3 lin)
|
|
{
|
|
return min(lin * vec3(12.92), pow(max(lin, vec3(0.00313067)), vec3(1.0/2.4)) * vec3(1.055) - vec3(0.055));
|
|
}
|
|
|
|
vec3 SRGBToLinear(vec3 Color)
|
|
{
|
|
Color.r = Color.r > 0.04045 ? pow(Color.r * (1.0 / 1.055) + 0.0521327, 2.4) : Color.r * (1.0 / 12.92);
|
|
Color.g = Color.g > 0.04045 ? pow(Color.g * (1.0 / 1.055) + 0.0521327, 2.4) : Color.g * (1.0 / 12.92);
|
|
Color.b = Color.b > 0.04045 ? pow(Color.b * (1.0 / 1.055) + 0.0521327, 2.4) : Color.b * (1.0 / 12.92);
|
|
return Color;
|
|
}
|
|
|
|
// https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/
|
|
vec3 ACESFilm(vec3 x)
|
|
{
|
|
float a = 2.51f;
|
|
float b = 0.03f;
|
|
float c = 2.43f;
|
|
float d = 0.59f;
|
|
float e = 0.14f;
|
|
return clamp((x*(a*x+b))/(x*(c*x+d)+e), 0.0, 1.0);
|
|
}
|
|
|
|
`;
|
|
|
|
var gl_shader = gl.createShader(shader_type);
|
|
gl.shaderSource(gl_shader, shader_std + shader_code);
|
|
gl.compileShader(gl_shader);
|
|
|
|
var compilation_status = gl.getShaderParameter(gl_shader, gl.COMPILE_STATUS);
|
|
if (!compilation_status)
|
|
{
|
|
var compilation_log = gl.getShaderInfoLog(gl_shader);
|
|
console.error('Shader compiler log: ' + compilation_log);
|
|
return compilation_log;
|
|
}
|
|
return gl_shader;
|
|
}
|
|
|
|
function gl_create_shader_program(gl, vert_shader, frag_shader)
|
|
{
|
|
var gl_vert_shader = null;
|
|
if (typeof vert_shader === 'string')
|
|
{
|
|
gl_vert_shader = gl_create_shader(gl, gl.VERTEX_SHADER, vert_shader);
|
|
if (typeof gl_vert_shader === 'string')
|
|
{
|
|
return gl_vert_shader;
|
|
}
|
|
}
|
|
else if (typeof vert_shader === 'WebGLShader')
|
|
{
|
|
gl_vert_shader = vert_shader;
|
|
}
|
|
else
|
|
{
|
|
console.error('wrong vert_shader');
|
|
return null;
|
|
}
|
|
|
|
var gl_frag_shader = null;
|
|
if (typeof frag_shader === 'string')
|
|
{
|
|
gl_frag_shader = gl_create_shader(gl, gl.FRAGMENT_SHADER, frag_shader);
|
|
if (typeof gl_frag_shader === 'string')
|
|
{
|
|
return gl_frag_shader;
|
|
}
|
|
}
|
|
else if (typeof frag_shader === 'WebGLShader')
|
|
{
|
|
gl_frag_shader = frag_shader;
|
|
}
|
|
else
|
|
{
|
|
console.error('wrong vert_shader');
|
|
return null;
|
|
}
|
|
|
|
var shader_program = gl.createProgram();
|
|
gl.attachShader(shader_program, gl_vert_shader);
|
|
gl.attachShader(shader_program, gl_frag_shader);
|
|
gl.linkProgram(shader_program);
|
|
|
|
var link_status = gl.getProgramParameter(shader_program, gl.LINK_STATUS);
|
|
if (!link_status)
|
|
{
|
|
var link_log = gl.getProgramInfoLog(shader_program);
|
|
console.error('Program compiler log: ' + link_log);
|
|
return null;
|
|
}
|
|
return shader_program;
|
|
}
|
|
|
|
function gl_set_uniform_uint(gl, program, name, value)
|
|
{
|
|
var uniform_loc = gl.getUniformLocation(program, name);
|
|
if (uniform_loc)
|
|
{
|
|
gl.uniform1ui(uniform_loc, value);
|
|
}
|
|
}
|
|
|
|
function gl_set_uniform_mat4(gl, program, name, matrix)
|
|
{
|
|
var uniform_loc = gl.getUniformLocation(program, name);
|
|
if (uniform_loc)
|
|
{
|
|
gl.uniformMatrix4fv(uniform_loc, false, new Float32Array(matrix));
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- MATH UTILS
|
|
|
|
function create_null_matrix()
|
|
{
|
|
return new Array(4 * 4).fill(0.0);
|
|
}
|
|
|
|
function create_identity_matrix()
|
|
{
|
|
var matrix = create_null_matrix();
|
|
matrix[0 + 0 * 4] = 1.0;
|
|
matrix[1 + 1 * 4] = 1.0;
|
|
matrix[2 + 2 * 4] = 1.0;
|
|
matrix[3 + 3 * 4] = 1.0;
|
|
return matrix;
|
|
}
|
|
|
|
function create_view_matrix(camera_pos, camera_dir)
|
|
{
|
|
var camera_pos_x = camera_pos[0];
|
|
var camera_pos_y = camera_pos[1];
|
|
var camera_pos_z = camera_pos[2];
|
|
|
|
var camera_dir_len = Math.sqrt(
|
|
camera_dir[0] * camera_dir[0] +
|
|
camera_dir[1] * camera_dir[1] +
|
|
camera_dir[2] * camera_dir[2]);
|
|
var camera_dir_x = camera_dir[0] / camera_dir_len;
|
|
var camera_dir_y = camera_dir[1] / camera_dir_len;
|
|
var camera_dir_z = camera_dir[2] / camera_dir_len;
|
|
|
|
var horizon_dir_len = Math.sqrt(camera_dir_x * camera_dir_x + camera_dir_y * camera_dir_y);
|
|
var horizon_dir_x = camera_dir_y / horizon_dir_len;
|
|
var horizon_dir_y = -camera_dir_x / horizon_dir_len;
|
|
var horizon_dir_z = 0.0;
|
|
|
|
var vertical_dir_x = - camera_dir_y * horizon_dir_z + camera_dir_z * horizon_dir_y;
|
|
var vertical_dir_y = - camera_dir_z * horizon_dir_x + camera_dir_x * horizon_dir_z;
|
|
var vertical_dir_z = - camera_dir_x * horizon_dir_y + camera_dir_y * horizon_dir_x;
|
|
|
|
// column major
|
|
var matrix = create_identity_matrix();
|
|
matrix[0 + 0 * 4] = horizon_dir_x;
|
|
matrix[0 + 1 * 4] = horizon_dir_y;
|
|
matrix[0 + 2 * 4] = horizon_dir_z;
|
|
matrix[0 + 3 * 4] = -(horizon_dir_x * camera_pos_x + horizon_dir_y * camera_pos_y + horizon_dir_z * camera_pos_z);
|
|
|
|
matrix[1 + 0 * 4] = vertical_dir_x;
|
|
matrix[1 + 1 * 4] = vertical_dir_y;
|
|
matrix[1 + 2 * 4] = vertical_dir_z;
|
|
matrix[1 + 3 * 4] = -(vertical_dir_x * camera_pos_x + vertical_dir_y * camera_pos_y + vertical_dir_z * camera_pos_z);
|
|
|
|
matrix[2 + 0 * 4] = camera_dir_x;
|
|
matrix[2 + 1 * 4] = camera_dir_y;
|
|
matrix[2 + 2 * 4] = camera_dir_z;
|
|
matrix[2 + 3 * 4] = -(camera_dir_x * camera_pos_x + camera_dir_y * camera_pos_y + camera_dir_z * camera_pos_z);
|
|
|
|
return matrix;
|
|
}
|
|
|
|
// Matches TPerspectiveMatrix
|
|
function create_projection_matrix_reverse_z_persepective(half_fov, aspect_ratio, clipping_plane)
|
|
{
|
|
const z_precision = 0.0;
|
|
|
|
var half_fov_x = half_fov * Math.PI / 180.0;
|
|
var half_fov_y = half_fov_x;
|
|
var mult_fov_x = 1.0;
|
|
var mult_fov_y = aspect_ratio;
|
|
var min_z = clipping_plane;
|
|
var max_z = clipping_plane;
|
|
|
|
// column major
|
|
var matrix = create_null_matrix();
|
|
matrix[0 + 0 * 4] = mult_fov_x / Math.tan(half_fov_x);
|
|
matrix[1 + 1 * 4] = mult_fov_y / Math.tan(half_fov_y);
|
|
matrix[2 + 3 * 4] = min_z;
|
|
matrix[3 + 2 * 4] = 1.0;
|
|
|
|
return matrix;
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY TEXTURE
|
|
|
|
var k_exotic_raw_channel_bit_depth = 0;
|
|
var g_shader_code_dict = {};
|
|
|
|
class TextureView extends ResourceView
|
|
{
|
|
constructor(subresource_version_info, resource_desc)
|
|
{
|
|
super(subresource_version_info, resource_desc);
|
|
this.is_ready = false;
|
|
this.raw_texture_data = null;
|
|
this.subresource_desc = null;
|
|
this.release_gl();
|
|
this.viewport_width = 1;
|
|
this.pixel_scaling = 0;
|
|
this.viewport_selected = false;
|
|
this.is_draging_canvas = false;
|
|
this.src_channels = 'RGB';
|
|
this.src_color_space = 'Gamut=sRGB Pixel Values';
|
|
this.display_mode = 'Visualization';
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
var subresource_extent = this.get_subresource_extent();
|
|
|
|
var dpi = window.devicePixelRatio || 1;
|
|
|
|
var canvas_rendering_res_x = subresource_extent['x'];
|
|
var canvas_rendering_res_y = subresource_extent['y'];
|
|
|
|
var canvas_display_w = canvas_rendering_res_x / dpi;
|
|
var canvas_display_h = canvas_rendering_res_y / dpi;
|
|
|
|
var resource_info_htmls = '';
|
|
{
|
|
resource_info_htmls += `
|
|
<table width="100%" class="pretty_table resource_desc">`;
|
|
|
|
for (var key in this.resource_desc)
|
|
{
|
|
if (this.resource_desc.hasOwnProperty(key)){
|
|
resource_info_htmls += `
|
|
<tr>
|
|
<td>${key}</td>
|
|
<td>${this.resource_desc[key]}</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
|
|
resource_info_htmls += `
|
|
</table>`;
|
|
}
|
|
|
|
var title = prettify_subresource_unique_name(this.subresource_version_info, this.resource_desc);
|
|
if (this.subresource_version_info['draw'] >= 0)
|
|
{
|
|
title += ` draw:${this.subresource_version_info['draw']}`;
|
|
}
|
|
|
|
var html = `
|
|
<div class="main_div">
|
|
<div class="selection_list_title">Texture visualization: ${title}</div>
|
|
<div id="canvas_outter" style="margin-bottom: 10px;">
|
|
<div id="canvas_header">
|
|
<div class="button_list" id="texture_visualization_change_pixel_scaling"><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_pixel_scaling(this);" value="Fit"/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_pixel_scaling(this);" value="1:1"/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_pixel_scaling(this);" value="2:1"/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_pixel_scaling(this);" value="4:1"/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_pixel_scaling(this);" value="8:1"/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_pixel_scaling(this);" value="16:1"/>
|
|
</div>
|
|
<div class="button_list" id="texture_visualization_change_src_channels"><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_channels(this);" value="RGB" title="Display the WebGL's visualization shader's display.rgb channels (default)."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_channels(this);" value="R" title="Display the WebGL's visualization shader's display.r channel."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_channels(this);" value="G" title="Display the WebGL's visualization shader's display.g channel."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_channels(this);" value="B" title="Display the WebGL's visualization shader's display.b channel."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_channels(this);" value="A" title="Display the WebGL's visualization shader's display.a channel."/>
|
|
</div>
|
|
<div class="button_list" id="texture_visualization_change_src_color"><!---
|
|
---><span>Src color:</span><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_color(this);" value="Gamut=sRGB Pixel Values" title="Performs no modifications to display to WebGL's sRGB gamut backbuffer (default)."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_color(this);" value="Gamut=sRGB Linear[0;1]" title="Applies a LinearToWebGLDisplayGamma(display.rgb); to output WebGL's sRGB gamut backbuffer."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_color(this);" value="Gamut=sRGB Linear ACES Film" title="Applies a LinearToWebGLDisplayGamma(ACESFilm(display.rgb)); to output WebGL's sRGB gamut backbuffer."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_color(this);" value="sRGB" title="Applies a LinearToWebGLDisplayGamma(SRGBToLinear(display.rgba)) To output to WebGL's sRGB gamut backbuffer."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_src_color(this);" value="Rec709" title="Applies LinearToWebGLDisplayGamma(Rec709ToLinear(display.rgb)) to output WebGL's sRGB gamut backbuffer."/>
|
|
</div>
|
|
<div class="button_list" id="texture_visualization_change_display_modes"><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_display_mode(this);" value="Visualization" title="Display the WebGL's visualization shader's display.rgb."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_display_mode(this);" value="NaN" title="Display any NaN in the raw texture."/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_display_mode(this);" value="Inf" title="Display any Inf in the raw texture."/>
|
|
</div>
|
|
<div class="button_list">
|
|
<input type="button" onclick="g_view.resource_view.copy_canvas_to_clipboard();" value="Copy to clipboard"/>
|
|
</div>
|
|
</div>
|
|
<div id="canvas_viewport">
|
|
<canvas
|
|
id="texture_visualization_canvas"
|
|
width="${canvas_rendering_res_x}"
|
|
height="${canvas_rendering_res_y}"
|
|
style="width: ${canvas_display_w}px; height: ${canvas_display_h}px;"></canvas>
|
|
</div>
|
|
<div id="canvas_footer">
|
|
<table>
|
|
<tr>
|
|
<td>Cursor texel pos:</td>
|
|
<td id="canvas_hover_texel_info"></td>
|
|
</tr>
|
|
<tr>
|
|
<td>Selected texel pos:</td>
|
|
<td id="canvas_selected_texel_info"></td>
|
|
</tr>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
<table width="100%">
|
|
<tr>
|
|
<td width="50%">
|
|
<div class="selection_list_title">Texture descriptor</div>
|
|
${resource_info_htmls}
|
|
</td>
|
|
<td style="width: 50%;">
|
|
<textarea id="texture_visualization_code_input" oninput="g_view.resource_view.shader_user_code_change();" onchange="g_view.resource_view.shader_user_code_change();"></textarea>
|
|
<textarea id="texture_visualization_code_log" readonly></textarea>
|
|
<div style="padding: 3px 10px;"><a class="external_link" href="https://www.khronos.org/files/webgl20-reference-guide.pdf">WebGL 2.0's quick reference card</a></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>`;
|
|
|
|
parent_dom.innerHTML = html;
|
|
|
|
update_value_selection(document.getElementById('texture_visualization_change_pixel_scaling'), `Fit`);
|
|
update_value_selection(document.getElementById('texture_visualization_change_src_channels'), this.src_channels);
|
|
update_value_selection(document.getElementById('texture_visualization_change_src_color'), this.src_color_space);
|
|
update_value_selection(document.getElementById('texture_visualization_change_display_modes'), this.display_mode);
|
|
|
|
// Init WebGL 2.0
|
|
this.init_gl();
|
|
|
|
// Translate the descriptor of the subresource.
|
|
this.subresource_desc = this.translate_subresource_desc();
|
|
if (this.subresource_desc === null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Setup mouse motion callback
|
|
{
|
|
var texture_view = this;
|
|
|
|
this.canvas.onclick = function(event) { texture_view.canvas_click(event); }
|
|
this.canvas.onmouseenter = function(event) { texture_view.canvas_onmousemove(event); };
|
|
this.canvas.onmousemove = function(event) { texture_view.canvas_onmousemove(event); };
|
|
this.canvas.onmousedown = function(event) { texture_view.canvas_onmousedown(event); };
|
|
this.canvas.onmouseup = function(event) { texture_view.canvas_onmouseup(event); };
|
|
this.canvas.oncontextmenu = function(event) { return false; };
|
|
|
|
document.getElementById('canvas_viewport').oncontextmenu = function(event) { return false; };
|
|
document.getElementById('canvas_viewport').onscrollend = function(event) { texture_view.save_zoom_settings(); };
|
|
|
|
document.getElementById('canvas_outter').onclick = function(event) { texture_view.onclick_canvas_outter(); };
|
|
document.getElementById('canvas_outter').onmouseleave = function(event) { texture_view.set_select_viewport(false); };
|
|
}
|
|
|
|
if (this.subresource_desc['raw_channel_format'] == 'float')
|
|
{
|
|
update_value_selection(document.getElementById('texture_visualization_change_display_modes'), this.display_mode);
|
|
}
|
|
else
|
|
{
|
|
document.getElementById('texture_visualization_change_display_modes').style.display = 'none';
|
|
}
|
|
|
|
// Fill out the default visualization shader code in the textarea for the user to customise.
|
|
{
|
|
var user_shader_code_dom = document.getElementById('texture_visualization_code_input');
|
|
|
|
if (this.shader_code_saving_key in g_shader_code_dict)
|
|
{
|
|
user_shader_code_dom.value = g_shader_code_dict[this.shader_code_saving_key];
|
|
}
|
|
else
|
|
{
|
|
if (!this.subresource_desc)
|
|
{
|
|
user_shader_code_dom.value = `// Sorry but ${this.resource_desc['Format']} is not supported for display :(`;
|
|
}
|
|
else
|
|
{
|
|
user_shader_code_dom.value = this.subresource_desc['webgl_pixel_shader_public'];
|
|
}
|
|
}
|
|
}
|
|
|
|
var texture_view = this;
|
|
var subresource_version_name = get_subresource_unique_version_name(this.subresource_version_info);
|
|
load_resource_binary_file(`Resources/${subresource_version_name}.bin`, function(raw_texture_data)
|
|
{
|
|
if (raw_texture_data)
|
|
{
|
|
texture_view.is_ready = true;
|
|
texture_view.raw_texture_data = raw_texture_data;
|
|
texture_view.resize_canvas(texture_view.pixel_scaling);
|
|
texture_view.process_texture_data_for_visualization();
|
|
texture_view.refresh_texture_visualization();
|
|
texture_view.onload();
|
|
}
|
|
else
|
|
{
|
|
texture_view.error(`Error: Resources/${subresource_version_name}.bin couldn't be loaded.`);
|
|
}
|
|
});
|
|
|
|
document.title = `${g_view.pass_data['EventName']} - ${this.resource_desc['Name']} ${this.subresource_version_info['subresource']}`;
|
|
}
|
|
|
|
error(error_msg)
|
|
{
|
|
console.error(error_msg);
|
|
document.getElementById('canvas_viewport').innerHTML = `
|
|
<div class="error_msg">
|
|
${error_msg}
|
|
</div>`;
|
|
document.getElementById('texture_visualization_code_input').readOnly = true;
|
|
}
|
|
|
|
image_load_uint(texel_x, texel_y)
|
|
{
|
|
console.assert(this.is_ready);
|
|
|
|
var extent = this.get_subresource_extent();
|
|
var pixel_data_offset = texel_x + texel_y * extent['x'];
|
|
|
|
var texel_value = [0];
|
|
for (var c = 1; c < this.subresource_desc['raw_channel_count']; c++)
|
|
{
|
|
texel_value.push(0);
|
|
}
|
|
|
|
if (this.subresource_desc['webgl_src_type'] === this.gl.UNSIGNED_SHORT_5_6_5)
|
|
{
|
|
var data = new Uint16Array(this.raw_texture_data, pixel_data_offset * 2, 1);
|
|
|
|
texel_value[0] = (data[0] >> 0) & 0x1F;
|
|
texel_value[1] = (data[0] >> 5) & 0x3F;
|
|
texel_value[2] = (data[0] >> 11) & 0x1F;
|
|
}
|
|
else if (this.subresource_desc['webgl_src_type'] === this.gl.UNSIGNED_INT_10F_11F_11F_REV)
|
|
{
|
|
var data = new Uint32Array(this.raw_texture_data, pixel_data_offset * 4, 1);
|
|
|
|
texel_value[0] = (data[0] >> 0) & 0x7FF;
|
|
texel_value[1] = (data[0] >> 11) & 0x7FF;
|
|
texel_value[2] = (data[0] >> 22) & 0x3FF;
|
|
}
|
|
else if (this.subresource_desc['webgl_src_type'] === this.gl.UNSIGNED_INT_2_10_10_10_REV)
|
|
{
|
|
var data = new Uint32Array(this.raw_texture_data, pixel_data_offset * 4, 1);
|
|
|
|
texel_value[0] = (data[0] >> 0) & 0x3FF;
|
|
texel_value[1] = (data[0] >> 10) & 0x3FF;
|
|
texel_value[2] = (data[0] >> 20) & 0x3FF;
|
|
texel_value[3] = (data[0] >> 30) & 0x3;
|
|
}
|
|
else
|
|
{
|
|
var data = null;
|
|
if (this.subresource_desc['raw_channel_bit_depth'] === 8)
|
|
{
|
|
data = new Uint8Array(this.raw_texture_data, this.subresource_desc['raw_channel_count'] * pixel_data_offset * 1, this.subresource_desc['raw_channel_count']);
|
|
}
|
|
else if (this.subresource_desc['raw_channel_bit_depth'] === 16)
|
|
{
|
|
data = new Uint16Array(this.raw_texture_data, this.subresource_desc['raw_channel_count'] * pixel_data_offset * 2, this.subresource_desc['raw_channel_count']);
|
|
}
|
|
else if (this.subresource_desc['raw_channel_bit_depth'] === 32)
|
|
{
|
|
data = new Uint32Array(this.raw_texture_data, this.subresource_desc['raw_channel_count'] * pixel_data_offset * 4, this.subresource_desc['raw_channel_count']);
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (var c = 0; c < this.subresource_desc['raw_channel_count']; c++)
|
|
{
|
|
texel_value[c] = data[this.subresource_desc['raw_channel_swizzling'][c]];
|
|
}
|
|
}
|
|
|
|
return texel_value;
|
|
}
|
|
|
|
decode_texel_uint(value)
|
|
{
|
|
console.assert(this.is_ready);
|
|
|
|
value = [...value];
|
|
|
|
if (this.subresource_desc['ue_pixel_format'] === 'PF_FloatRGB' || this.subresource_desc['ue_pixel_format'] === 'PF_FloatR11G11B10')
|
|
{
|
|
value[0] = decode_float11(value[0]);
|
|
value[1] = decode_float11(value[1]);
|
|
value[2] = decode_float10(value[2]);
|
|
}
|
|
else if (this.subresource_desc['ue_pixel_format'] === 'PF_A2B10G10R10')
|
|
{
|
|
value[0] = value[0] / 1023.0;
|
|
value[1] = value[1] / 1023.0;
|
|
value[2] = value[2] / 1023.0;
|
|
value[3] = value[3] / 3.0;
|
|
}
|
|
else if (this.subresource_desc['ue_pixel_format'] === 'PF_R5G6B5_UNORM')
|
|
{
|
|
value[0] = value[0] / 31.0;
|
|
value[1] = value[1] / 63.0;
|
|
value[2] = value[2] / 31.0;
|
|
}
|
|
else if (this.subresource_desc['raw_channel_format'] == 'uint')
|
|
{
|
|
// NOP
|
|
}
|
|
else if (this.subresource_desc['raw_channel_format'] == 'float')
|
|
{
|
|
for (var c = 0; c < value.length; c++)
|
|
{
|
|
if (this.subresource_desc['raw_channel_bit_depth'] == 16)
|
|
{
|
|
value[c] = decode_float16(value[c]);
|
|
}
|
|
else if (this.subresource_desc['raw_channel_bit_depth'] == 32)
|
|
{
|
|
value[c] = decode_float32(value[c]);
|
|
}
|
|
else
|
|
{
|
|
console.error('Unknown float bit depth');
|
|
}
|
|
}
|
|
}
|
|
else if (this.subresource_desc['raw_channel_format'] == 'unorm')
|
|
{
|
|
var divide = Number((BigInt(1) << BigInt(this.subresource_desc['raw_channel_bit_depth'])) - 1n);
|
|
|
|
for (var c = 0; c < value.length; c++)
|
|
{
|
|
value[c] = Number(value[c]) / divide;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
console.error('Unknown pixel format to convert');
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
update_texel_cursor_pos(texel_x, texel_y, parent_dom_name)
|
|
{
|
|
console.assert(this.is_ready);
|
|
|
|
var parent_dom = document.getElementById(parent_dom_name);
|
|
parent_dom.innerHTML = `<span style="width: 40px; display: inline-block; text-align: right;">${texel_x}</span> <span style="width: 40px; display: inline-block; text-align: right; margin-right: 60px;">${texel_y}</span> `;
|
|
|
|
// Find out pixel value.
|
|
{
|
|
var texel_raw = this.image_load_uint(texel_x, texel_y);
|
|
var texel_value = this.decode_texel_uint(texel_raw);
|
|
|
|
const channel_name = ['R', 'G', 'B', 'A'];
|
|
const channel_color = ['rgb(255, 38, 38)', 'rgb(38, 255, 38)', 'rgb(38, 187, 255)', 'white'];
|
|
|
|
var texel_string = [];
|
|
for (var c = 0; c < this.subresource_desc['raw_channel_count']; c++)
|
|
{
|
|
texel_string.push(`${texel_value[c].toString()} (0x${texel_raw[c].toString(16)})`);
|
|
}
|
|
|
|
if (this.subresource_version_info['subresource'] == 'stencil')
|
|
{
|
|
texel_string[0] = '0b' + texel_value[0].toString(2).padStart(8, 0);
|
|
}
|
|
|
|
var html = ``;
|
|
for (var c = 0; c < this.subresource_desc['raw_channel_count']; c++)
|
|
{
|
|
html += `<span style="margin-left: 20px; color: ${channel_color[c]};">${channel_name[c]}: <span style="min-width: 170px; display: inline-block; text-align: right;">${texel_string[c]}</span></span>`;
|
|
}
|
|
|
|
parent_dom.innerHTML += html;
|
|
}
|
|
}
|
|
|
|
get_texel_coordinate_of_mouse(event)
|
|
{
|
|
console.assert(this.is_ready);
|
|
|
|
var rect = event.target.getBoundingClientRect();
|
|
var html_x = event.clientX - rect.left;
|
|
var html_y = event.clientY - rect.top;
|
|
|
|
var texel_x = Math.floor(html_x * px_string_to_int(this.canvas.width) / px_string_to_int(this.canvas.style.width));
|
|
var texel_y = Math.floor(html_y * px_string_to_int(this.canvas.height) / px_string_to_int(this.canvas.style.height));
|
|
|
|
return [texel_x, texel_y];
|
|
}
|
|
|
|
onclick_canvas_outter()
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.set_select_viewport(true);
|
|
}
|
|
|
|
onmousewheel(event)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.viewport_selected == false)
|
|
{
|
|
// forward scroll event to parent
|
|
document.getElementById('main_right_pannel').scrollTop += event.deltaY;
|
|
return false;
|
|
}
|
|
|
|
var dpi = window.devicePixelRatio || 1;
|
|
var zooming_in = event.deltaY < 0.0;
|
|
|
|
var subresource_extent = this.get_subresource_extent();
|
|
|
|
// Find new pixel scaling to scale the viewport to.
|
|
var new_pixel_scaling = 0;
|
|
{
|
|
var viewport_width = (px_string_to_int(document.getElementById('canvas_viewport').style.width) - k_style_scroll_width) * dpi;
|
|
|
|
var min_pixel_scaling = Math.ceil(viewport_width / subresource_extent['x']);
|
|
if (min_pixel_scaling <= 1)
|
|
{
|
|
min_pixel_scaling = 1;
|
|
}
|
|
else if (min_pixel_scaling <= 2)
|
|
{
|
|
min_pixel_scaling = 2;
|
|
}
|
|
else if (min_pixel_scaling <= 4)
|
|
{
|
|
min_pixel_scaling = 4;
|
|
}
|
|
else if (min_pixel_scaling <= 8)
|
|
{
|
|
min_pixel_scaling = 8;
|
|
}
|
|
else if (min_pixel_scaling <= 16)
|
|
{
|
|
min_pixel_scaling = 16;
|
|
}
|
|
|
|
if (zooming_in)
|
|
{
|
|
if (this.pixel_scaling == 0)
|
|
{
|
|
if (min_pixel_scaling <= 16)
|
|
{
|
|
new_pixel_scaling = min_pixel_scaling;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
new_pixel_scaling = this.pixel_scaling * 2;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (this.pixel_scaling <= min_pixel_scaling)
|
|
{
|
|
new_pixel_scaling = 0;
|
|
}
|
|
else
|
|
{
|
|
new_pixel_scaling = this.pixel_scaling / 2;
|
|
}
|
|
}
|
|
|
|
new_pixel_scaling = Math.min(Math.max(new_pixel_scaling, 0), 16);
|
|
}
|
|
|
|
var [texel_x, texel_y] = this.get_texel_coordinate_of_mouse(event);
|
|
|
|
this.resize_canvas(new_pixel_scaling, texel_x, texel_y);
|
|
this.update_change_pixel_scaling_button();
|
|
this.save_zoom_settings();
|
|
|
|
return false;
|
|
}
|
|
|
|
update_change_pixel_scaling_button()
|
|
{
|
|
const pixel_scaling_names = {
|
|
0: 'Fit',
|
|
1: '1:1',
|
|
2: '2:1',
|
|
4: '4:1',
|
|
8: '8:1',
|
|
16: '16:1',
|
|
};
|
|
update_value_selection(document.getElementById('texture_visualization_change_pixel_scaling'), pixel_scaling_names[this.pixel_scaling]);
|
|
}
|
|
|
|
canvas_click(event)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var subresource_extent = this.get_subresource_extent();
|
|
|
|
var [texel_x, texel_y] = this.get_texel_coordinate_of_mouse(event);
|
|
|
|
texel_x = Math.min(Math.max(texel_x, 0), subresource_extent['x'] - 1);
|
|
texel_y = Math.min(Math.max(texel_y, 0), subresource_extent['y'] - 1);
|
|
|
|
this.update_texel_cursor_pos(texel_x, texel_y, 'canvas_selected_texel_info');
|
|
}
|
|
|
|
canvas_onmousemove(event)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (this.is_draging_canvas)
|
|
{
|
|
this.drag_canvas(event);
|
|
}
|
|
|
|
var subresource_extent = this.get_subresource_extent();
|
|
|
|
var [texel_x, texel_y] = this.get_texel_coordinate_of_mouse(event);
|
|
|
|
texel_x = Math.min(Math.max(texel_x, 0), subresource_extent['x'] - 1);
|
|
texel_y = Math.min(Math.max(texel_y, 0), subresource_extent['y'] - 1);
|
|
|
|
this.update_texel_cursor_pos(texel_x, texel_y, 'canvas_hover_texel_info');
|
|
}
|
|
|
|
canvas_onmousedown(event)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (event.button == 2)
|
|
{
|
|
this.set_select_viewport(true);
|
|
this.is_draging_canvas = true;
|
|
}
|
|
}
|
|
|
|
canvas_onmouseup(event)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (event.button == 2)
|
|
{
|
|
this.is_draging_canvas = false;
|
|
}
|
|
}
|
|
|
|
drag_canvas(event)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var canvas_viewport = document.getElementById('canvas_viewport');
|
|
canvas_viewport.scrollLeft -= event.movementX;
|
|
canvas_viewport.scrollTop -= event.movementY;
|
|
this.save_zoom_settings();
|
|
}
|
|
|
|
set_select_viewport(selected)
|
|
{
|
|
if (this.viewport_selected === selected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.viewport_selected = selected;
|
|
if (this.viewport_selected)
|
|
{
|
|
var texture_view = this;
|
|
document.getElementById('main_right_pannel').onwheel = function(event) { return false; };
|
|
document.getElementById('canvas_outter').classList.add('selected');
|
|
document.getElementById('canvas_viewport').style.overflow = 'scroll';
|
|
this.canvas.onwheel = function(event) { return texture_view.onmousewheel(event); };
|
|
}
|
|
else
|
|
{
|
|
document.getElementById('main_right_pannel').onwheel = null;
|
|
document.getElementById('canvas_outter').classList.remove('selected');
|
|
document.getElementById('canvas_viewport').style.overflow = 'hidden';
|
|
this.canvas.onwheel = null;
|
|
this.is_draging_canvas = false;
|
|
}
|
|
}
|
|
|
|
resize(ctx)
|
|
{
|
|
var dpi = window.devicePixelRatio || 1;
|
|
var viewport_width = ctx.width - 2 * k_style_padding_size - 2;
|
|
var viewport_width_no_scroll = viewport_width - k_style_scroll_width;
|
|
|
|
var subresource_extent = this.get_subresource_extent();
|
|
var canvas_rendering_res_x = subresource_extent['x'];
|
|
var canvas_rendering_res_y = subresource_extent['y'];
|
|
|
|
var canvas_outter = document.getElementById('canvas_outter');
|
|
var canvas_viewport = document.getElementById('canvas_viewport');
|
|
canvas_outter.style.width = `${viewport_width}px`;
|
|
canvas_viewport.style.width = `${viewport_width_no_scroll + k_style_scroll_width}px`;
|
|
canvas_viewport.style.height = `${viewport_width_no_scroll * canvas_rendering_res_y / canvas_rendering_res_x + k_style_scroll_width}px`;
|
|
|
|
this.viewport_width = viewport_width;
|
|
if (this.is_ready)
|
|
{
|
|
this.resize_canvas(this.pixel_scaling);
|
|
}
|
|
}
|
|
|
|
resize_canvas(new_pixel_scaling, fix_texel_x, fix_texel_y)
|
|
{
|
|
console.assert(this.is_ready);
|
|
var dpi = window.devicePixelRatio || 1;
|
|
var viewport_width = this.viewport_width;
|
|
var viewport_width_no_scroll = viewport_width - k_style_scroll_width;
|
|
|
|
var canvas_viewport = document.getElementById('canvas_viewport');
|
|
var canvas_viewport_rect = canvas_viewport.getBoundingClientRect();
|
|
var viewport_height = px_string_to_int(canvas_viewport.style.height);
|
|
var viewport_height_no_scroll = viewport_height - k_style_scroll_width;
|
|
|
|
var subresource_extent = this.get_subresource_extent();
|
|
var canvas_rendering_res_x = subresource_extent['x'];
|
|
var canvas_rendering_res_y = subresource_extent['y'];
|
|
|
|
if (fix_texel_x === undefined || fix_texel_y === undefined)
|
|
{
|
|
fix_texel_x = canvas_rendering_res_x / 2;
|
|
fix_texel_y = canvas_rendering_res_y / 2;
|
|
}
|
|
|
|
// Compute the pixel coordinate of the fixed texel.
|
|
var fix_texel_pixel_pos_x;
|
|
var fix_texel_pixel_pos_y;
|
|
{
|
|
var rect = this.canvas.getBoundingClientRect();
|
|
|
|
fix_texel_pixel_pos_x = rect.left + rect.width * fix_texel_x / canvas_rendering_res_x - canvas_viewport_rect.left;
|
|
fix_texel_pixel_pos_y = rect.top + rect.height * fix_texel_y / canvas_rendering_res_y - canvas_viewport_rect.top;
|
|
}
|
|
|
|
// Compute new canvas dimensions
|
|
var canvas_display_w = new_pixel_scaling * canvas_rendering_res_x / dpi;
|
|
var canvas_display_h = new_pixel_scaling * canvas_rendering_res_y / dpi;
|
|
if (new_pixel_scaling == 0)
|
|
{
|
|
canvas_display_w = viewport_width_no_scroll;
|
|
canvas_display_h = viewport_width_no_scroll * canvas_rendering_res_y / canvas_rendering_res_x;
|
|
}
|
|
|
|
// Compute new canvas scroll such that the fixed texel ends up at the same pixel coordinate.
|
|
var canvas_scroll_x;
|
|
var canvas_scroll_y;
|
|
{
|
|
canvas_scroll_x = fix_texel_x * canvas_display_w / canvas_rendering_res_x - fix_texel_pixel_pos_x;
|
|
canvas_scroll_y = fix_texel_y * canvas_display_h / canvas_rendering_res_y - fix_texel_pixel_pos_y;
|
|
}
|
|
|
|
this.canvas.style.width = `${canvas_display_w}px`;
|
|
this.canvas.style.height = `${canvas_display_h}px`;
|
|
canvas_viewport.scrollLeft = canvas_scroll_x;
|
|
canvas_viewport.scrollTop = canvas_scroll_y;
|
|
this.pixel_scaling = new_pixel_scaling;
|
|
|
|
this.save_zoom_settings();
|
|
}
|
|
|
|
get_zoom_settings()
|
|
{
|
|
var subresource_extent = this.get_subresource_extent();
|
|
|
|
var canvas_viewport = document.getElementById('canvas_viewport');
|
|
|
|
var zoom_settings = {
|
|
'subresource_extent_x': subresource_extent['x'],
|
|
'subresource_extent_y': subresource_extent['y'],
|
|
'pixel_scaling': this.pixel_scaling,
|
|
'canvas_width': this.canvas.style.width,
|
|
'canvas_height': this.canvas.style.height,
|
|
'canvas_viewport_scoll_left': canvas_viewport.scrollLeft,
|
|
'canvas_viewport_scoll_top': canvas_viewport.scrollTop,
|
|
};
|
|
return zoom_settings;
|
|
}
|
|
|
|
is_zoom_settings_compatible(zoom_settings)
|
|
{
|
|
var subresource_extent = this.get_subresource_extent();
|
|
|
|
return (zoom_settings['subresource_extent_x'] == subresource_extent['x'] && zoom_settings['subresource_extent_y'] == subresource_extent['y']);
|
|
}
|
|
|
|
apply_zoom_settings(zoom_settings)
|
|
{
|
|
var canvas_viewport = document.getElementById('canvas_viewport');
|
|
|
|
this.pixel_scaling = zoom_settings['pixel_scaling'];
|
|
this.canvas.style.width = zoom_settings['canvas_width'];
|
|
this.canvas.style.height = zoom_settings['canvas_height'];
|
|
canvas_viewport.scrollLeft = zoom_settings['canvas_viewport_scoll_left'];
|
|
canvas_viewport.scrollTop = zoom_settings['canvas_viewport_scoll_top'];
|
|
|
|
this.update_change_pixel_scaling_button();
|
|
this.save_zoom_settings();
|
|
}
|
|
|
|
save_zoom_settings()
|
|
{
|
|
set_local_storage(k_texture_zoom_local_storage, this.get_zoom_settings());
|
|
}
|
|
|
|
init_gl()
|
|
{
|
|
var gl_ctx_attributes = {
|
|
antialias: false,
|
|
depth: false
|
|
};
|
|
|
|
// Init WebGL 2.0
|
|
this.canvas = document.getElementById('texture_visualization_canvas');
|
|
|
|
var gl_ctx_attributes = {
|
|
alpha: false,
|
|
// premultipliedAlpha: true, // TODO
|
|
antialias: false,
|
|
depth: false,
|
|
stencil: false,
|
|
powerPreference: "low-power",
|
|
preserveDrawingBuffer: true,
|
|
xrCompatible: false,
|
|
};
|
|
|
|
var gl = this.canvas.getContext('webgl2', gl_ctx_attributes);
|
|
this.gl = gl;
|
|
|
|
verify_backbuffer_color_space(this.gl);
|
|
|
|
// Init WebGL extensions
|
|
{
|
|
var available_extensions = this.gl.getSupportedExtensions();
|
|
var required_extensions = ['EXT_texture_norm16'];
|
|
|
|
this.gl_ext = {};
|
|
|
|
var gl = this.gl;
|
|
var gl_ext = this.gl_ext;
|
|
required_extensions.forEach(function(ext_name)
|
|
{
|
|
gl_ext[ext_name] = gl.getExtension(ext_name);
|
|
});
|
|
}
|
|
|
|
// Set unpacking alignement to 1 to allow uploading texture size not multple of 4
|
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
|
|
// Create buffers
|
|
{
|
|
this.gl_vertex_buffer = gl_create_vertex_buffer(gl, new Float32Array([
|
|
-1.0,+1.0,0.0,
|
|
-1.0,-1.0,0.0,
|
|
+1.0,-1.0,0.0,
|
|
+1.0,+1.0,0.0,
|
|
]));
|
|
|
|
this.gl_index_buffer = gl_create_index_buffer(gl, new Uint16Array([0, 1, 2, 2, 0, 3]));
|
|
}
|
|
|
|
// Create simple shader program for NaN display mode
|
|
{
|
|
var frag_code = `
|
|
uniform sampler2D texture0;
|
|
|
|
in vec2 uv;
|
|
out vec4 display;
|
|
|
|
void main(void) {
|
|
ivec2 texel_coord = ivec2(uv * vec2(textureSize(texture0, 0)));
|
|
display = texelFetch(texture0, texel_coord, 0);
|
|
}`;
|
|
this.gl_simple_program = this.compile_screen_shader_program(frag_code);
|
|
}
|
|
|
|
// Create simple shader program for NaN display mode
|
|
{
|
|
var frag_code = `
|
|
uniform sampler2D texture0;
|
|
|
|
in vec2 uv;
|
|
out vec4 display;
|
|
|
|
void main(void)
|
|
{
|
|
ivec2 texel_coord = ivec2(uv * vec2(textureSize(texture0, 0)));
|
|
vec4 raw = texelFetch(texture0, texel_coord, 0);
|
|
|
|
display.r = (isnan(raw.r) || isnan(raw.g) || isnan(raw.b) || isnan(raw.a)) ? 1.0 : 0.0;
|
|
display.g = 0.0;
|
|
display.b = 0.0;
|
|
display.a = 1.0;
|
|
}`;
|
|
this.gl_nan_program = this.compile_screen_shader_program(frag_code);
|
|
}
|
|
|
|
// Create simple shader program for Inf display mode
|
|
{
|
|
var frag_code = `
|
|
uniform sampler2D texture0;
|
|
|
|
in vec2 uv;
|
|
out vec4 display;
|
|
|
|
void main(void)
|
|
{
|
|
ivec2 texel_coord = ivec2(uv * vec2(textureSize(texture0, 0)));
|
|
vec4 raw = texelFetch(texture0, texel_coord, 0);
|
|
|
|
display.r = (isinf(raw.r) || isinf(raw.g) || isinf(raw.b) || isinf(raw.a)) ? 1.0 : 0.0;
|
|
display.g = 0.0;
|
|
display.b = 0.0;
|
|
display.a = 1.0;
|
|
}`;
|
|
this.gl_inf_program = this.compile_screen_shader_program(frag_code);
|
|
}
|
|
}
|
|
|
|
release_gl()
|
|
{
|
|
this.canvas = null;
|
|
this.gl = null;
|
|
this.gl_ext = null;
|
|
this.gl_vertex_buffer = null;
|
|
this.gl_index_buffer = null;
|
|
this.gl_simple_program = null;
|
|
this.gl_nan_program = null;
|
|
this.gl_textures = null;
|
|
}
|
|
|
|
release()
|
|
{
|
|
this.is_ready = false;
|
|
this.raw_texture_data = null;
|
|
this.release_gl();
|
|
this.set_select_viewport(false);
|
|
}
|
|
|
|
shader_user_code_change()
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.refresh_texture_visualization();
|
|
}
|
|
|
|
copy_canvas_to_clipboard()
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var pass_data = g_view.pass_data;
|
|
|
|
var is_output_resource = is_pass_output_resource(pass_data, this.subresource_version_info);
|
|
|
|
var text = '';
|
|
text += `Dump:\n\t${g_infos['Project']} - ${g_infos['Platform']} - ${g_infos['RHI']} - ${g_infos['BuildVersion']} - ${g_infos['DumpTime']}\n\t${get_current_navigation()}\n`
|
|
text += `Pass:\n\t${pass_data['EventName']}\n`;
|
|
|
|
{
|
|
var name = prettify_subresource_unique_name(this.subresource_version_info, this.resource_desc);
|
|
text += `${is_output_resource ? 'Output' : 'Input'} resource:\n\t${name}\n`;
|
|
if (this.display_mode == 'Visualization')
|
|
{
|
|
text += `\tDisplay shader: \n${g_shader_code_dict[this.shader_code_saving_key]}\n`;
|
|
text += `\tDisplay channels: ${this.src_channels}\n`;
|
|
text += `\tDisplay src color: ${this.src_color_space}\n`;
|
|
}
|
|
else
|
|
{
|
|
text += `\tSrc color: ${this.display_mode}\n`;
|
|
}
|
|
}
|
|
|
|
if (this.subresource_version_info['draw'] >= 0)
|
|
{
|
|
text += `Draw ${this.subresource_version_info['draw']}:\n\t${g_view.pass_draws_data[this.subresource_version_info['draw']]['DrawName']}\n`;
|
|
}
|
|
|
|
copy_canvas_to_clipboard(this.canvas, text);
|
|
}
|
|
|
|
get shader_code_saving_key()
|
|
{
|
|
var name = '';
|
|
if (this.resource_desc['Name'])
|
|
{
|
|
name = this.resource_desc['Name'];
|
|
}
|
|
else
|
|
{
|
|
name = this.subresource_version_info['resource'];
|
|
}
|
|
|
|
name = `${name}.${this.resource_desc['Format']}`;
|
|
|
|
if (this.subresource_version_info['subresource'] && !this.subresource_version_info['subresource'].startsWith('mip'))
|
|
{
|
|
name = `${name}.${this.subresource_version_info['subresource']}`;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
get_default_texture_shader_code()
|
|
{
|
|
if (this.shader_code_saving_key in g_shader_code_dict)
|
|
{
|
|
return g_shader_code_dict[this.shader_code_saving_key];
|
|
}
|
|
|
|
if (!this.subresource_desc)
|
|
{
|
|
return `// Sorry but ${this.resource_desc['Format']} is not supported for display :(`;
|
|
}
|
|
|
|
return this.subresource_desc['webgl_pixel_shader'];
|
|
}
|
|
|
|
translate_subresource_desc()
|
|
{
|
|
var gl = this.gl;
|
|
var ue_pixel_format = this.resource_desc['Format'];
|
|
|
|
// Missing formats:
|
|
//PF_R8G8B8A8_SNORM
|
|
//PF_R16G16B16A16_UNORM
|
|
//PF_R16G16B16A16_SNORN
|
|
|
|
if (this.resource_desc['NumSamples'] > 1)
|
|
{
|
|
this.error(`MSAA is not supported for visualization.`);
|
|
return null;
|
|
}
|
|
|
|
var webgl_texture_internal_format = null;
|
|
var webgl_texture_src_format = null;
|
|
var webgl_texture_src_type = null;
|
|
var webgl_texture_count = 1;
|
|
var raw_channel_swizzling = [0, 1, 2, 3];
|
|
var raw_channel_bit_depth = 0;
|
|
var raw_channel_count = 0;
|
|
var raw_channel_format = 'exotic';
|
|
|
|
// 32bit floats
|
|
if (ue_pixel_format === 'PF_A32B32G32R32F')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA32F;
|
|
webgl_texture_src_format = gl.RGBA;
|
|
webgl_texture_src_type = gl.FLOAT;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'float';
|
|
}
|
|
else if (ue_pixel_format === 'PF_G32R32F')
|
|
{
|
|
webgl_texture_internal_format = gl.RG32F;
|
|
webgl_texture_src_format = gl.RG;
|
|
webgl_texture_src_type = gl.FLOAT;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'float';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R32_FLOAT' || ue_pixel_format === 'PF_BC4')
|
|
{
|
|
webgl_texture_internal_format = gl.R32F;
|
|
webgl_texture_src_format = gl.RED;
|
|
webgl_texture_src_type = gl.FLOAT;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'float';
|
|
}
|
|
// 16bit floats
|
|
else if (ue_pixel_format === 'PF_FloatRGBA' || ue_pixel_format === 'PF_BC6H' || ue_pixel_format === 'PF_BC7')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA16F;
|
|
webgl_texture_src_format = gl.RGBA;
|
|
webgl_texture_src_type = gl.HALF_FLOAT;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'float';
|
|
}
|
|
else if (ue_pixel_format === 'PF_G16R16F' || ue_pixel_format === 'PF_G16R16F_FILTER' || ue_pixel_format === 'PF_BC5')
|
|
{
|
|
webgl_texture_internal_format = gl.RG16F;
|
|
webgl_texture_src_format = gl.RG;
|
|
webgl_texture_src_type = gl.HALF_FLOAT;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'float';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R16F' || ue_pixel_format === 'PF_R16F_FILTER')
|
|
{
|
|
webgl_texture_internal_format = gl.R16F;
|
|
webgl_texture_src_format = gl.RED;
|
|
webgl_texture_src_type = gl.HALF_FLOAT;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'float';
|
|
}
|
|
// 16bit unorms
|
|
else if (ue_pixel_format === 'PF_A16B16G16R16')
|
|
{
|
|
webgl_texture_internal_format = this.gl_ext['EXT_texture_norm16'].RGBA16_EXT;
|
|
webgl_texture_src_format = gl.RGBA;
|
|
webgl_texture_src_type = gl.UNSIGNED_SHORT;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
else if (ue_pixel_format === 'PF_G16R16')
|
|
{
|
|
webgl_texture_internal_format = this.gl_ext['EXT_texture_norm16'].RG16_EXT;
|
|
webgl_texture_src_format = gl.RG;
|
|
webgl_texture_src_type = gl.UNSIGNED_SHORT;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
else if (ue_pixel_format === 'PF_G16')
|
|
{
|
|
webgl_texture_internal_format = this.gl_ext['EXT_texture_norm16'].R16_EXT;
|
|
webgl_texture_src_format = gl.RED;
|
|
webgl_texture_src_type = gl.UNSIGNED_SHORT;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
// 8bit unorms
|
|
else if (ue_pixel_format === 'PF_B8G8R8A8' || ue_pixel_format === 'PF_R8G8B8A8')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA;
|
|
webgl_texture_src_format = gl.RGBA;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'unorm';
|
|
|
|
if (ue_pixel_format === 'PF_B8G8R8A8' && g_infos['RHI'] !== 'OpenGL')
|
|
{
|
|
raw_channel_swizzling = [2, 1, 0, 3];
|
|
}
|
|
}
|
|
else if (ue_pixel_format === 'PF_R8G8')
|
|
{
|
|
webgl_texture_internal_format = gl.RG8;
|
|
webgl_texture_src_format = gl.RG;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R8' || ue_pixel_format === 'PF_G8' || ue_pixel_format === 'PF_A8' || ue_pixel_format === 'PF_L8')
|
|
{
|
|
webgl_texture_internal_format = gl.R8;
|
|
webgl_texture_src_format = gl.RED;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
// 32bit uint
|
|
else if (ue_pixel_format === 'PF_R32_UINT' || ue_pixel_format === 'PF_R32_SINT')
|
|
{
|
|
webgl_texture_internal_format = gl.R8UI;
|
|
webgl_texture_src_format = gl.RED_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
webgl_texture_count = 4;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R32G32B32A32_UINT')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA8UI;
|
|
webgl_texture_src_format = gl.RGBA_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
webgl_texture_count = 4;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R32G32_UINT' || ue_pixel_format === 'PF_R64_UINT')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA8UI;
|
|
webgl_texture_src_format = gl.RGBA_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
webgl_texture_count = 4;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
// 16bit uint
|
|
else if (ue_pixel_format === 'PF_R16_UINT' || ue_pixel_format === 'PF_R16_SINT')
|
|
{
|
|
webgl_texture_internal_format = gl.R8UI;
|
|
webgl_texture_src_format = gl.RED_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
webgl_texture_count = 2;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R16G16B16A16_UINT' || ue_pixel_format === 'PF_R16G16B16A16_SINT')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA8UI;
|
|
webgl_texture_src_format = gl.RGBA_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
webgl_texture_count = 2;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R16G16_UINT')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA8UI;
|
|
webgl_texture_src_format = gl.RGBA_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
webgl_texture_count = 2;
|
|
raw_channel_bit_depth = 16;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
// 8bit uint
|
|
else if (ue_pixel_format === 'PF_R8G8B8A8_UINT')
|
|
{
|
|
webgl_texture_internal_format = gl.RGBA8UI;
|
|
webgl_texture_src_format = gl.RGBA_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R8G8_UINT')
|
|
{
|
|
webgl_texture_internal_format = gl.RG8UI;
|
|
webgl_texture_src_format = gl.RG_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 2;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R8_UINT')
|
|
{
|
|
webgl_texture_internal_format = gl.R8UI;
|
|
webgl_texture_src_format = gl.RED_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
// exotic bit depth
|
|
else if (ue_pixel_format === 'PF_FloatRGB' || ue_pixel_format === 'PF_FloatR11G11B10')
|
|
{
|
|
webgl_texture_internal_format = gl.R11F_G11F_B10F;
|
|
webgl_texture_src_format = gl.RGB;
|
|
webgl_texture_src_type = gl.UNSIGNED_INT_10F_11F_11F_REV;
|
|
raw_channel_bit_depth = k_exotic_raw_channel_bit_depth;
|
|
raw_channel_count = 3;
|
|
raw_channel_format = 'float';
|
|
}
|
|
else if (ue_pixel_format === 'PF_A2B10G10R10')
|
|
{
|
|
webgl_texture_internal_format = gl.RGB10_A2;
|
|
webgl_texture_src_format = gl.RGBA;
|
|
webgl_texture_src_type = gl.UNSIGNED_INT_2_10_10_10_REV;
|
|
raw_channel_bit_depth = k_exotic_raw_channel_bit_depth;
|
|
raw_channel_count = 4;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
else if (ue_pixel_format === 'PF_R5G6B5_UNORM')
|
|
{
|
|
webgl_texture_internal_format = gl.RGB565;
|
|
webgl_texture_src_format = gl.RGB;
|
|
webgl_texture_src_type = gl.UNSIGNED_SHORT_5_6_5;
|
|
raw_channel_bit_depth = k_exotic_raw_channel_bit_depth;
|
|
raw_channel_count = 3;
|
|
raw_channel_format = 'unorm';
|
|
}
|
|
// depth stencil
|
|
else if (ue_pixel_format === 'PF_DepthStencil' || ue_pixel_format === 'PF_ShadowDepth' || ue_pixel_format === 'PF_D24')
|
|
{
|
|
if (this.subresource_version_info['subresource'] == 'stencil')
|
|
{
|
|
webgl_texture_internal_format = gl.R8UI;
|
|
webgl_texture_src_format = gl.RED_INTEGER;
|
|
webgl_texture_src_type = gl.UNSIGNED_BYTE;
|
|
raw_channel_bit_depth = 8;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'uint';
|
|
}
|
|
else
|
|
{
|
|
webgl_texture_internal_format = gl.R32F;
|
|
webgl_texture_src_format = gl.RED;
|
|
webgl_texture_src_type = gl.FLOAT;
|
|
raw_channel_bit_depth = 32;
|
|
raw_channel_count = 1;
|
|
raw_channel_format = 'float';
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this.error(`Pixel format ${ue_pixel_format} is not supported for visualization.`);
|
|
return null;
|
|
}
|
|
|
|
var shader_float_vector = [null, 'float', 'vec2', 'vec3', 'vec4'];
|
|
|
|
var shader_sampler_type = 'sampler2D';
|
|
var shader_sampler_return_type = shader_float_vector;
|
|
var shader_display_operation = 'texel';
|
|
if (webgl_texture_src_format === gl.RED_INTEGER || webgl_texture_src_format === gl.RG_INTEGER || webgl_texture_src_format === gl.RGBA_INTEGER)
|
|
{
|
|
var divide = (BigInt(1) << BigInt(raw_channel_bit_depth)) - 1n;
|
|
shader_sampler_type = 'usampler2D';
|
|
shader_sampler_return_type = [null, 'uint', 'uvec2', 'uvec3', 'uvec4'];
|
|
shader_display_operation = `${shader_float_vector[raw_channel_count]}(${shader_display_operation}) / ${divide}.0`;
|
|
}
|
|
|
|
var webgl_channel_count = 0;
|
|
if (webgl_texture_src_format === gl.RED || webgl_texture_src_format === gl.RED_INTEGER)
|
|
{
|
|
webgl_channel_count = 1;
|
|
}
|
|
else if (webgl_texture_src_format === gl.RG || webgl_texture_src_format === gl.RG_INTEGER)
|
|
{
|
|
webgl_channel_count = 2;
|
|
}
|
|
else if (webgl_texture_src_format === gl.RGB)
|
|
{
|
|
webgl_channel_count = 3;
|
|
}
|
|
else if (webgl_texture_src_format === gl.RGBA || webgl_texture_src_format === gl.RGBA_INTEGER)
|
|
{
|
|
webgl_channel_count = 4;
|
|
}
|
|
|
|
var webgl_pixel_shader_private = ``;
|
|
|
|
var shader_fetch_operation = '(\n';
|
|
for (var i = 0; i < webgl_texture_count; i++)
|
|
{
|
|
webgl_pixel_shader_private += `uniform ${shader_sampler_type} texture${i};\n`;
|
|
|
|
shader_fetch_operation += `\t\t(texelFetch(texture${i}, texel_coord, 0) << ${8 * i})`;
|
|
if (i + 1 == webgl_texture_count)
|
|
{
|
|
shader_fetch_operation += `)`;
|
|
}
|
|
else
|
|
{
|
|
shader_fetch_operation += ` |\n`;
|
|
}
|
|
}
|
|
|
|
if (webgl_texture_count == 1)
|
|
{
|
|
shader_fetch_operation = `texelFetch(texture0, texel_coord , 0)`;
|
|
}
|
|
|
|
const k_get_n_channels = new Array(
|
|
'',
|
|
'.r',
|
|
'.rg',
|
|
'.rgb',
|
|
'.rgba'
|
|
);
|
|
|
|
var shader_display_assignment = `display${k_get_n_channels[raw_channel_count]}`;
|
|
|
|
webgl_pixel_shader_private += `
|
|
${shader_sampler_return_type[raw_channel_count]} fetchTexel(vec2 uv)
|
|
{
|
|
ivec2 texel_coord = ivec2(uv * vec2(textureSize(texture0, 0)));
|
|
${shader_sampler_return_type[4]} raw = ${shader_fetch_operation};
|
|
return raw${k_get_n_channels[raw_channel_count]};
|
|
}
|
|
|
|
in vec2 uv;
|
|
out vec4 display;
|
|
`;
|
|
|
|
var webgl_pixel_shader_public = `// WebGL 2.0 visualization shader for a ${ue_pixel_format} ${this.subresource_version_info['subresource']}
|
|
|
|
${shader_sampler_return_type[raw_channel_count]} texel = fetchTexel(uv);
|
|
${shader_display_assignment} = ${shader_display_operation};`;
|
|
|
|
var gl_format = {};
|
|
gl_format['webgl_internal_format'] = webgl_texture_internal_format;
|
|
gl_format['webgl_src_format'] = webgl_texture_src_format;
|
|
gl_format['webgl_src_type'] = webgl_texture_src_type;
|
|
gl_format['webgl_texture_count'] = webgl_texture_count;
|
|
gl_format['webgl_pixel_shader_public'] = webgl_pixel_shader_public;
|
|
gl_format['webgl_pixel_shader_private'] = webgl_pixel_shader_private;
|
|
gl_format['webgl_channel_count'] = webgl_channel_count;
|
|
gl_format['raw_channel_swizzling'] = raw_channel_swizzling;
|
|
gl_format['raw_channel_bit_depth'] = raw_channel_bit_depth;
|
|
gl_format['raw_channel_count'] = raw_channel_count;
|
|
gl_format['raw_channel_format'] = raw_channel_format;
|
|
gl_format['ue_pixel_format'] = ue_pixel_format;
|
|
|
|
return gl_format;
|
|
}
|
|
|
|
change_pixel_scaling(pixel_scaling_button)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const pixel_scalings = {
|
|
'Fit': 0,
|
|
'1:1': 1,
|
|
'2:1': 2,
|
|
'4:1': 4,
|
|
'8:1': 8,
|
|
'16:1': 16,
|
|
};
|
|
this.resize_canvas(pixel_scalings[pixel_scaling_button.value]);
|
|
update_value_selection(document.getElementById('texture_visualization_change_pixel_scaling'), pixel_scaling_button.value);
|
|
}
|
|
|
|
change_src_channels(new_src_channels)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (new_src_channels.value == this.src_channels)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.src_channels = new_src_channels.value;
|
|
|
|
update_value_selection(document.getElementById('texture_visualization_change_src_channels'), this.src_channels);
|
|
|
|
this.refresh_texture_visualization();
|
|
}
|
|
|
|
change_src_color(new_src_color_space)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (new_src_color_space.value == this.src_color_space)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.src_color_space = new_src_color_space.value;
|
|
|
|
update_value_selection(document.getElementById('texture_visualization_change_src_color'), this.src_color_space);
|
|
|
|
this.refresh_texture_visualization();
|
|
}
|
|
|
|
change_display_mode(new_mode)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (new_mode.value == this.display_mode)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.display_mode = new_mode.value;
|
|
|
|
update_value_selection(document.getElementById('texture_visualization_change_display_modes'), this.display_mode);
|
|
|
|
if (this.display_mode == 'Visualization')
|
|
{
|
|
document.getElementById('texture_visualization_code_input').readOnly = false;
|
|
}
|
|
else
|
|
{
|
|
document.getElementById('texture_visualization_code_input').readOnly = true;
|
|
}
|
|
|
|
this.refresh_texture_visualization();
|
|
}
|
|
|
|
get_subresource_extent()
|
|
{
|
|
var extent = {};
|
|
extent['x'] = this.resource_desc['ExtentX'];
|
|
extent['y'] = this.resource_desc['ExtentY'];
|
|
|
|
if (this.subresource_version_info['subresource'].startsWith('mip'))
|
|
{
|
|
var mip_id = Number(this.subresource_version_info['subresource'].substring(3));
|
|
|
|
for (var i = 0; i < mip_id; i++)
|
|
{
|
|
extent['x'] = Math.floor(extent['x'] / 2);
|
|
extent['y'] = Math.floor(extent['y'] / 2);
|
|
}
|
|
}
|
|
|
|
return extent;
|
|
}
|
|
|
|
create_gl_texture(webgl_internal_format, webgl_src_format, webgl_src_type, extent, converted_data)
|
|
{
|
|
console.assert(this.is_ready);
|
|
var gl = this.gl;
|
|
|
|
var texture = gl.createTexture();
|
|
gl.activeTexture(gl.TEXTURE0);
|
|
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
gl.texImage2D(
|
|
gl.TEXTURE_2D,
|
|
/* level = */ 0,
|
|
webgl_internal_format,
|
|
extent['x'],
|
|
extent['y'],
|
|
/* border = */ 0,
|
|
webgl_src_format,
|
|
webgl_src_type,
|
|
converted_data);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
|
|
return texture;
|
|
}
|
|
|
|
process_texture_data_for_visualization()
|
|
{
|
|
console.assert(this.is_ready);
|
|
var gl = this.gl;
|
|
|
|
if (this.raw_texture_data === null)
|
|
{
|
|
console.error(`failed to load ${get_subresource_unique_version_name(this.subresource_version_info)}`);
|
|
return null;
|
|
}
|
|
|
|
var extent = this.get_subresource_extent();
|
|
var webgl_array_size = this.subresource_desc['webgl_channel_count'] * extent['x'] * extent['y'];
|
|
|
|
// Reset the gl textures
|
|
this.gl_textures = [];
|
|
|
|
// Exotic pixel format
|
|
if (this.subresource_desc['raw_channel_bit_depth'] == k_exotic_raw_channel_bit_depth)
|
|
{
|
|
var gl = this.gl;
|
|
var original_converted_data = null;
|
|
if (this.subresource_desc['webgl_src_type'] === gl.UNSIGNED_SHORT_5_6_5)
|
|
{
|
|
original_converted_data = new Uint16Array(this.raw_texture_data);
|
|
}
|
|
else if (this.subresource_desc['webgl_src_type'] === gl.UNSIGNED_INT_10F_11F_11F_REV || this.subresource_desc['webgl_src_type'] === gl.UNSIGNED_INT_2_10_10_10_REV)
|
|
{
|
|
original_converted_data = new Uint32Array(this.raw_texture_data);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
|
|
var texture = this.create_gl_texture(this.subresource_desc['webgl_internal_format'], this.subresource_desc['webgl_src_format'], this.subresource_desc['webgl_src_type'], extent, original_converted_data);
|
|
this.gl_textures.push(texture);
|
|
}
|
|
else if (this.subresource_desc['webgl_texture_count'] > 1)
|
|
{
|
|
var original_converted_data = null;
|
|
if (this.subresource_desc['raw_channel_bit_depth'] === 8)
|
|
{
|
|
original_converted_data = new Uint8Array(this.raw_texture_data);
|
|
}
|
|
else if (this.subresource_desc['raw_channel_bit_depth'] === 16)
|
|
{
|
|
original_converted_data = new Uint16Array(this.raw_texture_data);
|
|
}
|
|
else if (this.subresource_desc['raw_channel_bit_depth'] === 32)
|
|
{
|
|
original_converted_data = new Uint32Array(this.raw_texture_data);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (var tex_id = 0; tex_id < this.subresource_desc['webgl_texture_count']; tex_id++)
|
|
{
|
|
var converted_data = new Uint8Array(webgl_array_size);
|
|
|
|
for (var y = 0; y < extent['y']; y++)
|
|
{
|
|
for (var x = 0; x < extent['x']; x++)
|
|
{
|
|
for (var c = 0; c < this.subresource_desc['webgl_channel_count']; c++)
|
|
{
|
|
var texel_offset = x + extent['x'] * y;
|
|
|
|
var value = 0;
|
|
if (c < this.subresource_desc['raw_channel_count'])
|
|
{
|
|
var src = original_converted_data[texel_offset * this.subresource_desc['raw_channel_count'] + this.subresource_desc['raw_channel_swizzling'][c]];
|
|
value = (src >> (8 * tex_id)) % 256;
|
|
}
|
|
converted_data[texel_offset * this.subresource_desc['webgl_channel_count'] + c] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
var texture = this.create_gl_texture(this.subresource_desc['webgl_internal_format'], this.subresource_desc['webgl_src_format'], this.subresource_desc['webgl_src_type'], extent, converted_data);
|
|
this.gl_textures.push(texture);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var gl = this.gl;
|
|
var converted_data = null;
|
|
var original_converted_data = null;
|
|
if (this.subresource_desc['webgl_src_type'] === gl.UNSIGNED_BYTE)
|
|
{
|
|
original_converted_data = new Uint8Array(this.raw_texture_data);
|
|
converted_data = new Uint8Array(webgl_array_size);
|
|
}
|
|
else if (this.subresource_desc['webgl_src_type'] === gl.UNSIGNED_SHORT || this.subresource_desc['webgl_src_type'] === gl.HALF_FLOAT)
|
|
{
|
|
original_converted_data = new Uint16Array(this.raw_texture_data);
|
|
converted_data = new Uint16Array(webgl_array_size);
|
|
}
|
|
else if (this.subresource_desc['webgl_src_type'] === gl.FLOAT)
|
|
{
|
|
original_converted_data = new Float32Array(this.raw_texture_data);
|
|
converted_data = new Float32Array(webgl_array_size);
|
|
}
|
|
else
|
|
{
|
|
return null;
|
|
}
|
|
|
|
for (var y = 0; y < extent['y']; y++)
|
|
{
|
|
for (var x = 0; x < extent['x']; x++)
|
|
{
|
|
for (var c = 0; c < this.subresource_desc['webgl_channel_count']; c++)
|
|
{
|
|
var i = this.subresource_desc['webgl_channel_count'] * (x + extent['x'] * y);
|
|
|
|
var value = 0;
|
|
if (c < this.subresource_desc['raw_channel_count'])
|
|
{
|
|
value = original_converted_data[i + this.subresource_desc['raw_channel_swizzling'][c]];
|
|
}
|
|
converted_data[i + c] = value;
|
|
}
|
|
}
|
|
}
|
|
|
|
var texture = this.create_gl_texture(this.subresource_desc['webgl_internal_format'], this.subresource_desc['webgl_src_format'], this.subresource_desc['webgl_src_type'], extent, converted_data);
|
|
this.gl_textures.push(texture);
|
|
}
|
|
}
|
|
|
|
compile_screen_shader_program(frag_code)
|
|
{
|
|
var vert_code = `
|
|
in vec3 coordinates;
|
|
out highp vec2 uv;
|
|
void main(void) {
|
|
gl_Position = vec4(coordinates, 1.0);
|
|
uv.x = 0.5 + coordinates.x * 0.5;
|
|
uv.y = 0.5 - coordinates.y * 0.5;
|
|
}`;
|
|
|
|
var shader_program = gl_create_shader_program(this.gl, vert_code, frag_code);
|
|
if (typeof shader_program === 'string')
|
|
{
|
|
document.getElementById('texture_visualization_code_log').value = 'Compilation failed:\n' + shader_program;
|
|
return null;
|
|
}
|
|
else
|
|
{
|
|
document.getElementById('texture_visualization_code_log').value = 'Compilation succeeded';
|
|
}
|
|
return shader_program;
|
|
}
|
|
|
|
refresh_texture_visualization()
|
|
{
|
|
console.assert(this.is_ready);
|
|
console.assert(this.gl_textures);
|
|
|
|
var shader_program = null;
|
|
if (this.display_mode == 'NaN')
|
|
{
|
|
shader_program = this.gl_nan_program;
|
|
}
|
|
else if (this.display_mode == 'Inf')
|
|
{
|
|
shader_program = this.gl_inf_program;
|
|
}
|
|
else
|
|
{
|
|
var user_frag_code = document.getElementById('texture_visualization_code_input').value;
|
|
|
|
var final_frag_code = this.subresource_desc['webgl_pixel_shader_private'] + 'void main(void) { display = vec4(0.0, 0.0, 0.0, 0.0);\n';
|
|
final_frag_code += user_frag_code;
|
|
final_frag_code += '\n';
|
|
|
|
if (this.src_channels == 'R')
|
|
{
|
|
final_frag_code += 'display.rgb = vec3(display.r, 0.0, 0.0);';
|
|
}
|
|
else if (this.src_channels == 'G')
|
|
{
|
|
final_frag_code += 'display.rgb = vec3(0.0, display.g, 0.0);';
|
|
}
|
|
else if (this.src_channels == 'B')
|
|
{
|
|
final_frag_code += 'display.rgb = vec3(0.0, 0.0, display.b);';
|
|
}
|
|
else if (this.src_channels == 'A')
|
|
{
|
|
final_frag_code += 'display.rgb = display.aaa;';
|
|
}
|
|
|
|
// verify_backbuffer_color_space() check webgl is a sRGB gamut backbuffer
|
|
if (this.src_color_space == 'Gamut=sRGB Pixel Values')
|
|
{
|
|
// NOP because verify_backbuffer_color_space() check webgl is a sRGB backbuffer, that directly export pixel values
|
|
}
|
|
else if (this.src_color_space == 'Gamut=sRGB Linear[0;1]')
|
|
{
|
|
final_frag_code += 'display.rgb = LinearToWebGLDisplayGamma(display.rgb);';
|
|
}
|
|
else if (this.src_color_space == 'Gamut=sRGB Linear ACES Film')
|
|
{
|
|
final_frag_code += 'display.rgb = LinearToWebGLDisplayGamma(ACESFilm(display.rgb));';
|
|
}
|
|
else if (this.src_color_space == 'sRGB')
|
|
{
|
|
final_frag_code += 'display.rgb = LinearToWebGLDisplayGamma(SRGBToLinear(display.rgb));';
|
|
}
|
|
else if (this.src_color_space == 'Rec709')
|
|
{
|
|
final_frag_code += 'display.rgb = LinearToWebGLDisplayGamma(Rec709ToLinear(display.rgb));';
|
|
}
|
|
final_frag_code += '}';
|
|
|
|
shader_program = this.compile_screen_shader_program(final_frag_code);
|
|
|
|
if (!shader_program)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Save the user code if compilation have succeeded.
|
|
g_shader_code_dict[this.shader_code_saving_key] = user_frag_code;
|
|
}
|
|
|
|
var gl = this.gl;
|
|
gl.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
{
|
|
gl.useProgram(shader_program);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.gl_vertex_buffer);
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.gl_index_buffer);
|
|
|
|
var coord = gl.getAttribLocation(shader_program, "coordinates");
|
|
gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
|
|
gl.enableVertexAttribArray(coord);
|
|
|
|
for (var tex_id = 0; tex_id < this.gl_textures.length; tex_id++)
|
|
{
|
|
gl.activeTexture(gl.TEXTURE0 + tex_id);
|
|
gl.bindTexture(gl.TEXTURE_2D, this.gl_textures[tex_id]);
|
|
|
|
var texture_sampler = gl.getUniformLocation(shader_program, 'texture' + tex_id);
|
|
gl.uniform1i(texture_sampler, tex_id);
|
|
}
|
|
|
|
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
|
|
}
|
|
}
|
|
} // class TextureView
|
|
|
|
function display_texture(subresource_version_info)
|
|
{
|
|
if (g_view.resource_view !== null && get_subresource_unique_version_name(g_view.resource_view.subresource_version_info) == get_subresource_unique_version_name(subresource_version_info))
|
|
{
|
|
return;
|
|
}
|
|
|
|
var resource_desc = get_resource_desc(subresource_version_info['resource']);
|
|
g_view.set_resource_view(new TextureView(subresource_version_info, resource_desc));
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- DISPLAY BUFFER
|
|
|
|
const k_buffer_pixel_per_row = 20;
|
|
const k_buffer_max_display_row_count = 50;
|
|
const k_buffer_display_row_buffer = 50;
|
|
|
|
|
|
class GenericBufferView extends ResourceView
|
|
{
|
|
constructor(subresource_version_info, resource_desc)
|
|
{
|
|
super(subresource_version_info, resource_desc);
|
|
this.raw_buffer_data = null;
|
|
this.raw_buffer_data_path = null;
|
|
this.viewport_selected = false;
|
|
this.display_row_start = 0;
|
|
this.selected_address = -1;
|
|
this.structure_metadata = null;
|
|
this.display_elem_using_rows = resource_desc['NumElements'] == 1;
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
var html = '';
|
|
|
|
if (this.resource_desc['Desc'] != 'FRDGParameterStruct')
|
|
{
|
|
var resource_info_htmls = `
|
|
<table width="100%" class="resource_desc pretty_table">`;
|
|
|
|
for (var key in this.resource_desc)
|
|
{
|
|
if (this.resource_desc.hasOwnProperty(key))
|
|
{
|
|
var value = this.resource_desc[key];
|
|
|
|
if (key == 'Metadata')
|
|
{
|
|
if (this.resource_desc[key] == k_null_json_ptr)
|
|
{
|
|
value = 'nullptr';
|
|
}
|
|
else if (this.structure_metadata)
|
|
{
|
|
value = `${this.structure_metadata['StructTypeName']} (${get_filename(this.structure_metadata['FileName'])}:${this.structure_metadata['FileLine']})`;
|
|
}
|
|
}
|
|
|
|
resource_info_htmls += `
|
|
<tr>
|
|
<td>${key}</td>
|
|
<td>${value}</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
|
|
resource_info_htmls += `
|
|
</table>`;
|
|
|
|
html += `
|
|
<div class="main_div">
|
|
<div class="selection_list_title">Buffer descriptor: ${this.resource_desc['Name']}</div>
|
|
<div>
|
|
${resource_info_htmls}
|
|
</div>
|
|
</div>`;
|
|
}
|
|
|
|
html += `
|
|
<div class="main_div">
|
|
<div class="selection_list_title" style="width: auto;">Buffer visualization: ${this.resource_desc['Name']}</div>
|
|
<div id="buffer_outter">
|
|
<div id="buffer_viewport_header">
|
|
<table width="100%" id="buffer_visualization_format_outter">
|
|
<tr>
|
|
<td style="padding: 4px 20px 0 20px; text-align: right;" width="40px">Format:</td>
|
|
<td>
|
|
<input type="text" id="buffer_visualization_format" onchange="g_view.resource_view.refresh_buffer_visualization(true);" style="width: 100%;" />
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div id="buffer_visualization_member_search_outter">
|
|
<input type="search" id="buffer_visualization_member_search" oninput="g_view.resource_view.refresh_members();" onchange="g_view.resource_view.refresh_members();" style="width: 100%;" placeholder="Search member..." />
|
|
</div>
|
|
</div>
|
|
<div id="buffer_viewport">
|
|
<div id="buffer_content_pannel"></div>
|
|
</div>
|
|
</div>
|
|
</div>`;
|
|
|
|
parent_dom.innerHTML = html;
|
|
|
|
if (this.structure_metadata)
|
|
{
|
|
// document.getElementById('buffer_visualization_format').value = `${this.structure_metadata['StructTypeName']} (${get_filename(this.structure_metadata['FileName'])}:${this.structure_metadata['FileLine']})`;
|
|
// document.getElementById('buffer_visualization_format').readOnly = true;
|
|
document.getElementById('buffer_visualization_format_outter').style.display = 'none';
|
|
}
|
|
else
|
|
{
|
|
var default_format = '';
|
|
{
|
|
var byte_per_element = this.resource_desc['BytesPerElement'];
|
|
var member_count = byte_per_element / 4;
|
|
|
|
var default_format = 'hex(uint)';
|
|
|
|
// DrawIndirect flags means this is certainly a 32bit uint.
|
|
if ('Usage' in this.resource_desc && this.resource_desc['Usage'].includes('DrawIndirect'))
|
|
{
|
|
default_format = 'uint';
|
|
}
|
|
|
|
var format_per_column = [];
|
|
for (var i = 0; i < member_count; i++)
|
|
{
|
|
format_per_column.push(default_format);
|
|
}
|
|
|
|
default_format = format_per_column.join(', ');
|
|
}
|
|
|
|
document.getElementById('buffer_visualization_format').value = default_format;
|
|
document.getElementById('buffer_visualization_member_search_outter').style.display = 'none';
|
|
}
|
|
|
|
// Setup mouse callbacks
|
|
{
|
|
var buffer_view = this;
|
|
|
|
document.getElementById('buffer_outter').onclick = function(event) { buffer_view.onclick_buffer_outter(event); };
|
|
document.getElementById('buffer_outter').onmouseleave = function(event) { buffer_view.set_select_viewport(false); };
|
|
document.getElementById('buffer_viewport').onclick = function(event) { buffer_view.onclick_buffer_viewport(event); };
|
|
document.getElementById('buffer_viewport').onscroll = function(event) { buffer_view.update_buffer_scroll(/* force_refresh = */ false); };
|
|
}
|
|
|
|
var buffer_view = this;
|
|
console.assert(this.raw_buffer_data_path);
|
|
load_resource_binary_file(this.raw_buffer_data_path, function(raw_buffer_data)
|
|
{
|
|
if (raw_buffer_data)
|
|
{
|
|
buffer_view.raw_buffer_data = raw_buffer_data;
|
|
buffer_view.refresh_buffer_visualization(/* refresh header = */ true);
|
|
buffer_view.onload();
|
|
}
|
|
else
|
|
{
|
|
document.getElementById('buffer_content_pannel').innerHTML = `
|
|
Error: ${this.raw_buffer_data_path} couldn't be loaded.`;
|
|
}
|
|
});
|
|
|
|
document.title = `${g_view.pass_data['EventName']} - ${this.resource_desc['Name']}`;
|
|
}
|
|
|
|
refresh_members()
|
|
{
|
|
this.refresh_buffer_visualization(/* refresh_header = */ !this.display_elem_using_rows);
|
|
}
|
|
|
|
onclick_buffer_outter(event)
|
|
{
|
|
this.set_select_viewport(true);
|
|
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var content_rect = document.getElementById('buffer_content_pannel').getBoundingClientRect();
|
|
var header_rect = document.getElementById('buffer_content_header').getBoundingClientRect();
|
|
|
|
var html_x = event.clientX - content_rect.left;
|
|
var html_y = event.clientY - content_rect.top - header_rect.height;
|
|
|
|
var new_selected_address = Math.floor(html_y / k_buffer_pixel_per_row);
|
|
|
|
if (new_selected_address != this.selected_address && new_selected_address < this.resource_desc['NumElements'])
|
|
{
|
|
this.selected_address = new_selected_address;
|
|
this.refresh_buffer_visualization();
|
|
}
|
|
}
|
|
|
|
onclick_buffer_viewport(event)
|
|
{
|
|
this.set_select_viewport(true);
|
|
}
|
|
|
|
set_select_viewport(selected)
|
|
{
|
|
if (this.viewport_selected === selected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.viewport_selected = selected;
|
|
if (this.viewport_selected)
|
|
{
|
|
var buffer_view = this;
|
|
document.getElementById('buffer_outter').classList.add('selected');
|
|
document.getElementById('buffer_viewport').style.overflow = 'scroll';
|
|
//document.getElementById('buffer_viewport').style.margin = '0px 0px 0px 0px';
|
|
}
|
|
else
|
|
{
|
|
document.getElementById('buffer_outter').classList.remove('selected');
|
|
document.getElementById('buffer_viewport').style.overflow = 'hidden';
|
|
//document.getElementById('buffer_viewport').style.margin = `0px ${k_scroll_width}px ${k_scroll_width}px 0px`;
|
|
}
|
|
}
|
|
|
|
update_buffer_scroll(force_refresh)
|
|
{
|
|
var buffer_viewport = document.getElementById('buffer_viewport');
|
|
|
|
var new_display_row_start = Math.max(2 * Math.floor((buffer_viewport.scrollTop / k_buffer_pixel_per_row) * 0.5), 0);
|
|
|
|
if (new_display_row_start != this.display_row_start || force_refresh)
|
|
{
|
|
this.display_row_start = new_display_row_start;
|
|
this.refresh_buffer_visualization(/* refresh_header = */ false);
|
|
}
|
|
}
|
|
|
|
go_to_address()
|
|
{
|
|
var byte_per_element = this.resource_desc['BytesPerElement'];
|
|
|
|
var address = document.getElementById('buffer_address_input').value;
|
|
if (address == '')
|
|
{
|
|
if (this.selected_address != -1)
|
|
{
|
|
this.selected_address = -1;
|
|
this.update_buffer_scroll(/* force_refresh = */ true);
|
|
}
|
|
return;
|
|
}
|
|
|
|
var element_id = Math.floor(Number(address) / byte_per_element);
|
|
if (element_id < this.resource_desc['NumElements'])
|
|
{
|
|
this.selected_address = element_id;
|
|
|
|
var buffer_viewport = document.getElementById('buffer_viewport');
|
|
buffer_viewport.scrollTop = (element_id - 5) * k_buffer_pixel_per_row;
|
|
|
|
this.update_buffer_scroll(/* force_refresh = */ true);
|
|
}
|
|
}
|
|
|
|
refresh_buffer_visualization(refresh_header)
|
|
{
|
|
var byte_per_element = this.resource_desc['BytesPerElement'];
|
|
var num_element = this.resource_desc['NumElements'];
|
|
|
|
var member_count = 0;
|
|
var member_names = [];
|
|
var member_byte_offset = [];
|
|
var member_format = [];
|
|
if (this.structure_metadata)
|
|
{
|
|
var member_search = document.getElementById('buffer_visualization_member_search').value;
|
|
|
|
iterate_structure_members(this.structure_metadata, function(it) {
|
|
var column_format = undefined;
|
|
if (it.base_type == 'UBMT_INT32')
|
|
{
|
|
column_format = 'int';
|
|
}
|
|
else if (it.base_type == 'UBMT_UINT32')
|
|
{
|
|
column_format = 'uint';
|
|
}
|
|
else if (it.base_type == 'UBMT_FLOAT32')
|
|
{
|
|
column_format = 'float';
|
|
}
|
|
else
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (member_search)
|
|
{
|
|
if (!it.cpp_name.toLowerCase().includes(member_search.toLowerCase()))
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
const k_suffixes = ['.x', '.y', '.z', '.w'];
|
|
|
|
var relative_offset = 0;
|
|
for (var elem_id = 0; elem_id < Math.max(it.member['NumElements'], 1); elem_id++)
|
|
{
|
|
for (var row_id = 0; row_id < Math.max(it.member['NumRows'], 1); row_id++)
|
|
{
|
|
for (var column_id = 0; column_id < it.member['NumColumns']; column_id++)
|
|
{
|
|
var name = it.cpp_name;
|
|
|
|
if (it.member['NumElements'] > 0)
|
|
{
|
|
name += `[${elem_id}]`;
|
|
}
|
|
if (it.member['NumRows'] > 1)
|
|
{
|
|
name += k_suffixes[row_id];
|
|
}
|
|
if (it.member['NumColumns'] > 1)
|
|
{
|
|
name += k_suffixes[column_id];
|
|
}
|
|
|
|
member_count += 1;
|
|
member_byte_offset.push(it.byte_offset + relative_offset);
|
|
member_format.push(column_format);
|
|
member_names.push(name);
|
|
|
|
relative_offset += 4;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
else
|
|
{
|
|
member_count = byte_per_element / 4;
|
|
|
|
var text_format = document.getElementById('buffer_visualization_format').value;
|
|
|
|
var column_formats = text_format.split(',');
|
|
|
|
var byte_offset = 0;
|
|
|
|
column_formats.forEach(function(value, index) {
|
|
var format_to_use = value.trim();
|
|
|
|
member_byte_offset.push(byte_offset);
|
|
member_format.push(format_to_use);
|
|
member_names.push(format_to_use);
|
|
|
|
byte_offset += 4;
|
|
});
|
|
|
|
for (var i = member_format.length; i < member_count; i++)
|
|
{
|
|
var format_to_use = member_format[i % column_formats.length];
|
|
|
|
member_byte_offset.push(byte_offset);
|
|
member_format.push(format_to_use);
|
|
member_names.push(format_to_use);
|
|
|
|
byte_offset += 4;
|
|
}
|
|
|
|
}
|
|
|
|
var column_count = member_count;
|
|
var row_count = num_element;
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
if (this.structure_metadata)
|
|
{
|
|
column_count = 2;
|
|
}
|
|
else
|
|
{
|
|
column_count = 1;
|
|
}
|
|
row_count = member_count;
|
|
}
|
|
|
|
var display_start_row_id = Math.max(this.display_row_start - k_buffer_display_row_buffer, 0);
|
|
var display_row_count = Math.min(row_count - display_start_row_id, k_buffer_max_display_row_count + 2 * k_buffer_display_row_buffer);
|
|
var display_end_row_id = display_start_row_id + display_row_count;
|
|
|
|
var scroll_top = document.getElementById('buffer_viewport').scrollTop;
|
|
|
|
if (refresh_header)
|
|
{
|
|
var address_search_value = '';
|
|
if (document.getElementById('buffer_address_input'))
|
|
{
|
|
address_search_value = document.getElementById('buffer_address_input').value;
|
|
}
|
|
|
|
var classes = 'pretty_table';
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
classes += ' display_elem_using_rows';
|
|
}
|
|
|
|
var resource_content = `
|
|
<table id="buffer_content_table" class="${classes}" width="100%">
|
|
<tr class="header" id="buffer_content_header">`;
|
|
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
resource_content += `<td>Member</td><td>Value</td>`;
|
|
if (this.structure_metadata)
|
|
{
|
|
resource_content += `<td>Hex</td>`;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
resource_content += `<td><input type="text" id="buffer_address_input" onchange="g_view.resource_view.go_to_address();" style="width: 100%;" placeholder="Address..." value="${address_search_value}" /></td>`;
|
|
for (var i = 0; i < member_count; i++)
|
|
{
|
|
resource_content += `<td>${member_names[i]}</td>`;
|
|
}
|
|
}
|
|
|
|
resource_content += `
|
|
<td></td>
|
|
</tr>
|
|
</table>`;
|
|
|
|
document.getElementById('buffer_content_pannel').innerHTML = resource_content;
|
|
}
|
|
else
|
|
{
|
|
var table_dom = document.getElementById("buffer_content_table");
|
|
while (table_dom.lastElementChild != table_dom.firstElementChild)
|
|
{
|
|
table_dom.removeChild(table_dom.lastElementChild);
|
|
}
|
|
}
|
|
|
|
{
|
|
// Start padding
|
|
var resource_content = ``;
|
|
{
|
|
resource_content += `
|
|
<tr>
|
|
<td style="height: ${k_buffer_pixel_per_row * display_start_row_id}px; padding: 0;"></td>
|
|
</tr>`;
|
|
}
|
|
|
|
for (var row_id = display_start_row_id; row_id < display_end_row_id; row_id++)
|
|
{
|
|
var elem_id = row_id;
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
elem_id = 0;
|
|
}
|
|
|
|
var classes = '';
|
|
if (elem_id == this.selected_address)
|
|
{
|
|
classes = 'highlighted';
|
|
}
|
|
|
|
resource_content += `
|
|
<tr class="${classes}">`;
|
|
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
resource_content += `
|
|
<td>${member_names[row_id]}</td>`;
|
|
}
|
|
else
|
|
{
|
|
resource_content += `
|
|
<td>0x${(elem_id * byte_per_element).toString(16)}</td>`;
|
|
}
|
|
|
|
for (var i = 0; i < column_count; i++)
|
|
{
|
|
var member_id = i;
|
|
if (this.display_elem_using_rows)
|
|
{
|
|
member_id = row_id;
|
|
}
|
|
|
|
var byte_offset = byte_per_element * elem_id + member_byte_offset[member_id];
|
|
|
|
var value = null;
|
|
var display = member_format[member_id];
|
|
|
|
if (this.display_elem_using_rows && i == 1 && this.structure_metadata)
|
|
{
|
|
display = `hex(${display})`;
|
|
}
|
|
|
|
if (display == 'float')
|
|
{
|
|
value = decode_float32((new Uint32Array(this.raw_buffer_data, byte_offset, 1))[0]);
|
|
}
|
|
else if (display == 'half')
|
|
{
|
|
value = decode_float16((new Uint16Array(this.raw_buffer_data, byte_offset, 1))[0]);
|
|
}
|
|
else if (display == 'int')
|
|
{
|
|
value = (new Int32Array(this.raw_buffer_data, byte_offset, 1))[0];
|
|
}
|
|
else if (display == 'uint')
|
|
{
|
|
value = (new Uint32Array(this.raw_buffer_data, byte_offset, 1))[0];
|
|
}
|
|
else if (display == 'short')
|
|
{
|
|
value = (new Int16Array(this.raw_buffer_data, byte_offset, 1))[0];
|
|
}
|
|
else if (display == 'ushort')
|
|
{
|
|
value = (new Uint16Array(this.raw_buffer_data, byte_offset, 1))[0];
|
|
}
|
|
else if (display == 'char')
|
|
{
|
|
value = (new Int8Array(this.raw_buffer_data, byte_offset, 1))[0];
|
|
}
|
|
else if (display == 'uchar')
|
|
{
|
|
value = (new Uint8Array(this.raw_buffer_data, byte_offset, 1))[0];
|
|
}
|
|
else if (display == 'hex(int)' || display == 'hex(uint)' || display == 'hex(float)')
|
|
{
|
|
value = '0x' + (new Uint32Array(this.raw_buffer_data, byte_offset, 1))[0].toString(16).padStart(8, 0);
|
|
}
|
|
else if (display == 'hex(short)' || display == 'hex(ushort)' || display == 'hex(half)')
|
|
{
|
|
value = '0x' + (new Uint16Array(this.raw_buffer_data, byte_offset, 1))[0].toString(16).padStart(4, 0);
|
|
}
|
|
else if (display == 'hex(char)' || display == 'hex(uchar)')
|
|
{
|
|
value = '0x' + (new Uint8Array(this.raw_buffer_data, byte_offset, 1))[0].toString(16).padStart(2, 0);
|
|
}
|
|
else if (display == 'bin(int)' || display == 'bin(uint)' || display == 'bin(float)')
|
|
{
|
|
value = '0b' + (new Uint32Array(this.raw_buffer_data, byte_offset, 1))[0].toString(2).padStart(32, 0);
|
|
}
|
|
else if (display == 'bin(short)' || display == 'bin(ushort)' || display == 'bin(half)')
|
|
{
|
|
value = '0b' + (new Uint16Array(this.raw_buffer_data, byte_offset, 1))[0].toString(2).padStart(16, 0);
|
|
}
|
|
else if (display == 'bin(char)' || display == 'bin(uchar)')
|
|
{
|
|
value = '0b' + (new Uint8Array(this.raw_buffer_data, byte_offset, 1))[0].toString(2).padStart(8, 0);
|
|
}
|
|
else
|
|
{
|
|
value = `Unknown ${display}`;
|
|
}
|
|
|
|
resource_content += `<td>${value}</td>`;
|
|
}
|
|
|
|
resource_content += `
|
|
<td></td>
|
|
</tr>`;
|
|
} // for (var row_id = display_start_row_id; row_id < display_end_row_id; row_id++)
|
|
|
|
if (row_count == 0)
|
|
{
|
|
resource_content += `
|
|
<tr>
|
|
<td colspan="${column_count + 2}" class="empty" align="center">Empty</td>
|
|
</tr>`;
|
|
}
|
|
|
|
// End padding
|
|
{
|
|
resource_content += `
|
|
<tr>
|
|
<td style="height: ${k_buffer_pixel_per_row * (row_count - display_end_row_id)}px; padding: 0;"></td>
|
|
</tr>`;
|
|
}
|
|
|
|
document.getElementById('buffer_content_table').innerHTML += resource_content;
|
|
}
|
|
|
|
document.getElementById('buffer_viewport').scrollTop = scroll_top;
|
|
|
|
{
|
|
var min_row_count = 10;
|
|
document.getElementById('buffer_viewport').style.height = `${k_style_scroll_width + Math.min(Math.max(row_count + 1, min_row_count), k_buffer_max_display_row_count) * k_buffer_pixel_per_row}px`;
|
|
}
|
|
}
|
|
|
|
release()
|
|
{
|
|
this.raw_buffer_data = null;
|
|
}
|
|
} // class GenericBufferView
|
|
|
|
|
|
class BufferView extends GenericBufferView
|
|
{
|
|
constructor(subresource_version_info, resource_desc)
|
|
{
|
|
super(subresource_version_info, resource_desc);
|
|
this.raw_buffer_data_path = `Resources/${get_subresource_unique_version_name(this.subresource_version_info)}.bin`;
|
|
|
|
if (resource_desc['Metadata'] != k_null_json_ptr)
|
|
{
|
|
this.structure_metadata = load_structure_metadata(resource_desc['Metadata']);
|
|
}
|
|
}
|
|
} // class BufferView
|
|
|
|
|
|
// -------------------------------------------------------------------- PARAMETER STRUCTURE
|
|
|
|
class ParameterStructureView extends GenericBufferView
|
|
{
|
|
constructor(subresource_version_info, resource_desc, structure_metadata)
|
|
{
|
|
super(subresource_version_info, resource_desc);
|
|
this.raw_buffer_data_path = `Structures/${subresource_version_info['resource']}.bin`;
|
|
this.structure_metadata = structure_metadata;
|
|
}
|
|
|
|
} // class ParameterStructureView
|
|
|
|
function display_pass_parameters(pass_id)
|
|
{
|
|
display_pass_internal(pass_id);
|
|
|
|
var pass_data = g_passes[pass_id];
|
|
var structure_metadata = load_structure_metadata(pass_data['ParametersMetadata']);
|
|
|
|
var subresource_version_info = {};
|
|
subresource_version_info['subresource'] = null;
|
|
subresource_version_info['resource'] = pass_data['Parameters'];
|
|
subresource_version_info['pass'] = pass_data['Pointer'];
|
|
subresource_version_info['draw'] = -1;
|
|
|
|
var resource_desc = {};
|
|
resource_desc['Name'] = 'PassParameters';
|
|
resource_desc['ByteSize'] = structure_metadata['Size'];
|
|
resource_desc['Desc'] = 'FRDGParameterStruct';
|
|
resource_desc['BytesPerElement'] = resource_desc['ByteSize'];
|
|
resource_desc['NumElements'] = 1;
|
|
resource_desc['Metadata'] = pass_data['ParametersMetadata'];
|
|
resource_desc['Usage'] = [];
|
|
|
|
g_view.set_resource_view(new ParameterStructureView(subresource_version_info, resource_desc, structure_metadata));
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- GENERIC 3D STRUCTURE VIEW
|
|
|
|
class Generic3DStructureView extends ResourceView
|
|
{
|
|
constructor(subresource_version_info, resource_desc)
|
|
{
|
|
super(subresource_version_info, resource_desc);
|
|
this.is_ready = false;
|
|
this.release();
|
|
this.camera_pos = [200.0, 200.0, 200.0];
|
|
this.camera_fov = 90.0;
|
|
this.camera_longitude = 0.0;
|
|
this.camera_latitude = 0.0;
|
|
this.camera_look_at([0.0, 0.0, 0.0]);
|
|
this.camera_movement_u = 0;
|
|
this.camera_movement_v = 0;
|
|
this.camera_movement_z = 0;
|
|
this.camera_movement_speed = 100.0; // 1 m/s
|
|
this.prev_time = 0;
|
|
}
|
|
|
|
setup_html(parent_dom)
|
|
{
|
|
var resource_info_htmls = '';
|
|
{
|
|
resource_info_htmls += `
|
|
<table width="100%" class="pretty_table resource_desc">`;
|
|
|
|
for (var key in this.resource_desc)
|
|
{
|
|
if (this.resource_desc.hasOwnProperty(key)){
|
|
resource_info_htmls += `
|
|
<tr>
|
|
<td>${key}</td>
|
|
<td>${this.resource_desc[key]}</td>
|
|
</tr>`;
|
|
}
|
|
}
|
|
|
|
resource_info_htmls += `
|
|
</table>`;
|
|
}
|
|
|
|
var title = prettify_subresource_unique_name(this.subresource_version_info, this.resource_desc);
|
|
if (this.subresource_version_info['draw'] >= 0)
|
|
{
|
|
title += ` draw:${this.subresource_version_info['draw']}`;
|
|
}
|
|
|
|
var html = `
|
|
<div class="main_div">
|
|
<div class="selection_list_title">Ray-tracing Acceleration Structure visualization: ${title}</div>
|
|
<div id="canvas_outter">
|
|
<div id="canvas_header">
|
|
<div class="button_list">
|
|
<input type="button" onclick="g_view.resource_view.copy_canvas_to_clipboard();" value="Copy to clipboard"/>
|
|
</div>
|
|
</div>
|
|
<canvas
|
|
id="generic_3d_visualization_canvas"
|
|
width="100"
|
|
height="100"
|
|
style="width: 100px; height: 100px;"></canvas>
|
|
</div>
|
|
<table width="100%">
|
|
<tr>
|
|
<td width="50%">
|
|
<div class="selection_list_title">Buffer descriptor</div>
|
|
${resource_info_htmls}
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</div>`;
|
|
|
|
parent_dom.innerHTML = html;
|
|
|
|
// Init WebGL 2.0
|
|
this.init_gl();
|
|
|
|
// Setup mouse motion callback
|
|
{
|
|
var generic_3d_view = this;
|
|
|
|
this.canvas.onclick = function(event) { generic_3d_view.onclick_canvas(); };
|
|
document.getElementById('canvas_outter').onmouseleave = function(event) { generic_3d_view.set_select_viewport(false); }
|
|
}
|
|
|
|
this.init();
|
|
}
|
|
|
|
init_gl()
|
|
{
|
|
var gl_ctx_attributes = {
|
|
antialias: false,
|
|
depth: false
|
|
};
|
|
|
|
// Init WebGL 2.0
|
|
this.canvas = document.getElementById('generic_3d_visualization_canvas');
|
|
|
|
var gl_ctx_attributes = {
|
|
alpha: true,
|
|
// premultipliedAlpha: true, // TODO
|
|
antialias: false,
|
|
depth: true,
|
|
stencil: false,
|
|
powerPreference: "low-power",
|
|
preserveDrawingBuffer: true,
|
|
xrCompatible: false,
|
|
};
|
|
|
|
var gl = this.canvas.getContext('webgl2', gl_ctx_attributes);
|
|
this.gl = gl;
|
|
|
|
verify_backbuffer_color_space(gl);
|
|
|
|
// Init WebGL extensions
|
|
{
|
|
var available_extensions = this.gl.getSupportedExtensions();
|
|
var required_extensions = ['EXT_texture_norm16'];
|
|
|
|
this.gl_ext = {};
|
|
|
|
var gl = this.gl;
|
|
var gl_ext = this.gl_ext;
|
|
required_extensions.forEach(function(ext_name)
|
|
{
|
|
gl_ext[ext_name] = gl.getExtension(ext_name);
|
|
});
|
|
}
|
|
|
|
// Create unit cube buffers exactly like GUnitCubeVertexBuffer and GUnitCubeIndexBuffer
|
|
{
|
|
var unit_cube_vertices = new Array(8 * 3);
|
|
for (var z = 0; z < 2; z++)
|
|
{
|
|
for (var y = 0; y < 2; y++)
|
|
{
|
|
for (var x = 0; x < 2; x++)
|
|
{
|
|
var dest = x * 4 + y * 2 + z;
|
|
unit_cube_vertices[dest * 3 + 0] = x ? -100.0 : 100.0;
|
|
unit_cube_vertices[dest * 3 + 1] = y ? -100.0 : 100.0;
|
|
unit_cube_vertices[dest * 3 + 2] = z ? -100.0 : 100.0;
|
|
}
|
|
}
|
|
}
|
|
|
|
var unit_cube_indices = [
|
|
0, 2, 3,
|
|
0, 3, 1,
|
|
4, 5, 7,
|
|
4, 7, 6,
|
|
0, 1, 5,
|
|
0, 5, 4,
|
|
2, 6, 7,
|
|
2, 7, 3,
|
|
0, 4, 6,
|
|
0, 6, 2,
|
|
1, 3, 7,
|
|
1, 7, 5,
|
|
];
|
|
|
|
this.gl_unit_cube = {}
|
|
this.gl_unit_cube.vertex_buffer = gl_create_vertex_buffer(gl, new Float32Array(unit_cube_vertices));
|
|
this.gl_unit_cube.index_buffer = gl_create_index_buffer(gl, new Uint16Array(unit_cube_indices));
|
|
this.gl_unit_cube.index_count = unit_cube_indices.length;
|
|
}
|
|
|
|
// Create simple shader program
|
|
{
|
|
var vert_code = `
|
|
uniform mat4 local_to_world;
|
|
uniform mat4 world_to_view;
|
|
uniform mat4 view_to_clip;
|
|
|
|
in vec3 vertex_coordinates;
|
|
out vec3 interpolator_world;
|
|
|
|
void main(void) {
|
|
vec4 world_pos = local_to_world * vec4(vertex_coordinates.xyz, 1.0);
|
|
vec4 view_pos = world_to_view * world_pos;
|
|
vec4 clip_pos = view_to_clip * view_pos;
|
|
|
|
gl_Position = clip_pos;
|
|
interpolator_world = world_pos.xyz;
|
|
}`;
|
|
|
|
var frag_code = `
|
|
in vec3 interpolator_world;
|
|
out vec4 display;
|
|
|
|
void main(void) {
|
|
vec3 normal = normalize(cross(dFdx(interpolator_world), dFdy(interpolator_world)));
|
|
|
|
display = vec4(normal * 0.5 + 0.5, 1.0);
|
|
}`;
|
|
|
|
this.gl_shader_program_simple = gl_create_shader_program(gl, vert_code, frag_code);
|
|
}
|
|
|
|
// Set unpacking alignement to 1 to allow uploading texture size not multple of 4
|
|
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
}
|
|
|
|
release_gl()
|
|
{
|
|
this.canvas = null;
|
|
this.gl = null;
|
|
this.gl_ext = null;
|
|
this.gl_unit_cube_vertex_buffer = null;
|
|
this.gl_unit_cube_index_buffer = null;
|
|
this.gl_shader_program_simple = null;
|
|
}
|
|
|
|
draw_canvas()
|
|
{
|
|
console.error('unimplemented');
|
|
}
|
|
|
|
init()
|
|
{
|
|
console.error('unimplemented');
|
|
}
|
|
|
|
release()
|
|
{
|
|
this.viewport_selected = false;
|
|
this.release_gl();
|
|
this.set_select_viewport(false);
|
|
}
|
|
|
|
resize(ctx)
|
|
{
|
|
var dpi = window.devicePixelRatio || 1;
|
|
var viewport_width = ctx.width - 2 * k_style_padding_size - 2;
|
|
var viewport_height = viewport_width * 9.0 / 16.0;
|
|
|
|
var canvas_outter = document.getElementById('canvas_outter');
|
|
|
|
this.canvas.style.width = `${viewport_width}px`;
|
|
this.canvas.style.height = `${viewport_height}px`;
|
|
this.canvas.width = Math.round(viewport_width * dpi);
|
|
this.canvas.height = Math.round(viewport_height * dpi);
|
|
}
|
|
|
|
onclick_canvas()
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.set_select_viewport(!this.viewport_selected);
|
|
}
|
|
|
|
set_select_viewport(selected)
|
|
{
|
|
if (this.viewport_selected === selected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.viewport_selected = selected;
|
|
|
|
this.canvas.oncontextmenu = function(event) { return false; };
|
|
|
|
var canvas_outter = document.getElementById('canvas_outter');
|
|
|
|
if (this.viewport_selected)
|
|
{
|
|
var generic_3d_view = this;
|
|
canvas_outter.classList.add('selected');
|
|
//this.canvas.style.cursor = 'none';
|
|
this.canvas.onmousemove = function(event) { generic_3d_view.canvas_onmousemove(event); };
|
|
this.canvas.onwheel = function(event) { return generic_3d_view.canvas_onmousewheel(event); };
|
|
|
|
this.keydown_event = function(event) { generic_3d_view.canvas_onkeydown(event); };
|
|
this.keyup_event = function(event) { generic_3d_view.canvas_onkeyup(event); };
|
|
document.addEventListener(
|
|
"keydown",
|
|
this.keydown_event,
|
|
false);
|
|
document.addEventListener(
|
|
"keyup",
|
|
this.keyup_event,
|
|
false);
|
|
|
|
this.shedule_next_tick(performance.now());
|
|
}
|
|
else
|
|
{
|
|
canvas_outter.classList.remove('selected');
|
|
this.canvas.style.cursor = 'default';
|
|
this.canvas.onmousemove = null;
|
|
this.canvas.onwheel = null;
|
|
|
|
document.removeEventListener(
|
|
"keydown",
|
|
this.keydown_event);
|
|
document.removeEventListener(
|
|
"keyup",
|
|
this.keyup_event);
|
|
|
|
this.camera_movement_u = 0;
|
|
this.camera_movement_v = 0;
|
|
this.camera_movement_z = 0;
|
|
}
|
|
}
|
|
|
|
canvas_onmousemove(event)
|
|
{
|
|
var dx = event.movementX;
|
|
var dy = event.movementY;
|
|
|
|
var speed = (Math.PI / 180.0);
|
|
|
|
this.camera_longitude -= dx * speed;
|
|
this.camera_latitude = Math.min(Math.max(this.camera_latitude - dy * speed, -0.4 * Math.PI), 0.4 * Math.PI);
|
|
}
|
|
|
|
canvas_onmousewheel(event)
|
|
{
|
|
var zooming_in = event.deltaY < 0.0;
|
|
|
|
if (zooming_in)
|
|
{
|
|
this.camera_movement_speed *= 2.0;
|
|
}
|
|
else
|
|
{
|
|
this.camera_movement_speed *= 0.5;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
canvas_onkeydown(event)
|
|
{
|
|
if (event.key == 'w')
|
|
{
|
|
this.camera_movement_z = 1.0;
|
|
}
|
|
else if (event.key == 's')
|
|
{
|
|
this.camera_movement_z = -1.0;
|
|
}
|
|
else if (event.key == 'q')
|
|
{
|
|
this.camera_movement_v = 1.0;
|
|
}
|
|
else if (event.key == 'e')
|
|
{
|
|
this.camera_movement_v = -1.0;
|
|
}
|
|
else if (event.key == 'a')
|
|
{
|
|
this.camera_movement_u = -1.0;
|
|
}
|
|
else if (event.key == 'd')
|
|
{
|
|
this.camera_movement_u = 1.0;
|
|
}
|
|
}
|
|
|
|
canvas_onkeyup(event)
|
|
{
|
|
if (event.key == 'w' && this.camera_movement_z > 0.0)
|
|
{
|
|
this.camera_movement_z = 0.0;
|
|
}
|
|
else if (event.key == 's' && this.camera_movement_z < 0.0)
|
|
{
|
|
this.camera_movement_z = 0.0;
|
|
}
|
|
else if (event.key == 'q' && this.camera_movement_v > 0.0)
|
|
{
|
|
this.camera_movement_v = 0.0;
|
|
}
|
|
else if (event.key == 'e' && this.camera_movement_v < 0.0)
|
|
{
|
|
this.camera_movement_v = 0.0;
|
|
}
|
|
else if (event.key == 'a' && this.camera_movement_u < 0.0)
|
|
{
|
|
this.camera_movement_u = 0.0;
|
|
}
|
|
else if (event.key == 'd' && this.camera_movement_u > 0.0)
|
|
{
|
|
this.camera_movement_u = 0.0;
|
|
}
|
|
}
|
|
|
|
copy_canvas_to_clipboard()
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var pass_data = g_view.pass_data;
|
|
|
|
var is_output_resource = is_pass_output_resource(pass_data, this.subresource_version_info);
|
|
|
|
var text = '';
|
|
text += `Dump:\n\t${g_infos['Project']} - ${g_infos['Platform']} - ${g_infos['RHI']} - ${g_infos['BuildVersion']} - ${g_infos['DumpTime']}\n\t${get_current_navigation()}\n`
|
|
text += `Pass:\n\t${pass_data['EventName']}\n`;
|
|
|
|
{
|
|
var name = prettify_subresource_unique_name(this.subresource_version_info, this.resource_desc);
|
|
text += `${is_output_resource ? 'Output' : 'Input'} resource:\n\t${name}\n`;
|
|
}
|
|
|
|
copy_canvas_to_clipboard(this.canvas, text);
|
|
}
|
|
|
|
camera_look_at(pos)
|
|
{
|
|
var camera_dir_x = pos[0] - this.camera_pos[0];
|
|
var camera_dir_y = pos[1] - this.camera_pos[1];
|
|
var camera_dir_z = pos[2] - this.camera_pos[2];
|
|
var camera_dir_len = Math.sqrt(camera_dir_x * camera_dir_x + camera_dir_y * camera_dir_y + camera_dir_z * camera_dir_z);
|
|
|
|
this.camera_longitude = Math.atan2(camera_dir_y / camera_dir_len, camera_dir_x / camera_dir_len);
|
|
this.camera_latitude = Math.asin(camera_dir_z / camera_dir_len);
|
|
}
|
|
|
|
get_view_matrix()
|
|
{
|
|
var camera_dir = [
|
|
Math.cos(this.camera_longitude) * Math.cos(this.camera_latitude),
|
|
Math.sin(this.camera_longitude) * Math.cos(this.camera_latitude),
|
|
Math.sin(this.camera_latitude),
|
|
];
|
|
|
|
return create_view_matrix(this.camera_pos, camera_dir);
|
|
}
|
|
|
|
get_proj_matrix()
|
|
{
|
|
if (false)
|
|
{
|
|
var width = 4.0;
|
|
var depth = 100.0;
|
|
|
|
var matrix = create_null_matrix();
|
|
matrix[0 + 0 * 4] = 1.0 / width;
|
|
matrix[1 + 1 * 4] = matrix[0 + 0 * 4] * this.canvas.width / this.canvas.height;
|
|
matrix[2 + 2 * 4] = - 1.0 / depth;
|
|
matrix[2 + 3 * 4] = 1.0;
|
|
matrix[3 + 3 * 4] = 1.0;
|
|
return matrix;
|
|
}
|
|
|
|
return create_projection_matrix_reverse_z_persepective(
|
|
this.camera_fov * 0.5, this.canvas.width / this.canvas.height, /* clipping_plane = */ 10.0);
|
|
}
|
|
|
|
shedule_next_tick(current_time)
|
|
{
|
|
var generic_3d_view = this;
|
|
this.prev_time = current_time;
|
|
requestAnimationFrame((new_time) => {
|
|
generic_3d_view.tick(new_time);
|
|
});
|
|
}
|
|
|
|
tick(current_time)
|
|
{
|
|
if (!this.viewport_selected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var delta_seconds = current_time - this.prev_time;
|
|
|
|
{
|
|
var delta_movements = 0.01 * delta_seconds * this.camera_movement_speed;
|
|
|
|
var camera_dir = [
|
|
Math.cos(this.camera_longitude) * Math.cos(this.camera_latitude),
|
|
Math.sin(this.camera_longitude) * Math.cos(this.camera_latitude),
|
|
Math.sin(this.camera_latitude),
|
|
];
|
|
|
|
var camera_dir_u = [
|
|
Math.sin(this.camera_longitude),
|
|
-Math.cos(this.camera_longitude),
|
|
0.0,
|
|
];
|
|
|
|
var camera_dir_v = [
|
|
Math.cos(this.camera_longitude) * Math.sin(-this.camera_latitude),
|
|
Math.sin(this.camera_longitude) * Math.sin(-this.camera_latitude),
|
|
Math.cos(this.camera_latitude),
|
|
];
|
|
|
|
this.camera_pos[0] += delta_movements * (
|
|
this.camera_movement_z * camera_dir[0] +
|
|
this.camera_movement_u * camera_dir_u[0] +
|
|
this.camera_movement_v * camera_dir_v[0]);
|
|
this.camera_pos[1] += delta_movements * (
|
|
this.camera_movement_z * camera_dir[1] +
|
|
this.camera_movement_u * camera_dir_u[1] +
|
|
this.camera_movement_v * camera_dir_v[1]);
|
|
this.camera_pos[2] += delta_movements * (
|
|
this.camera_movement_z * camera_dir[2] +
|
|
this.camera_movement_u * camera_dir_u[2] +
|
|
this.camera_movement_v * camera_dir_v[2]);
|
|
}
|
|
|
|
this.draw_canvas();
|
|
this.shedule_next_tick(current_time);
|
|
}
|
|
} // class Generic3DStructureView
|
|
|
|
|
|
// -------------------------------------------------------------------- HARDWARE ACCELERATION STRUCTURE
|
|
|
|
class RaytracingAccelerationStructureView extends Generic3DStructureView
|
|
{
|
|
constructor(subresource_version_info, resource_desc)
|
|
{
|
|
super(subresource_version_info, resource_desc);
|
|
this.draw_calls = [];
|
|
this.scene_visualization_shader_programs = {};
|
|
this.scene_visualization = 'Instances';
|
|
}
|
|
|
|
init()
|
|
{
|
|
document.getElementById('canvas_header').innerHTML += `
|
|
<div class="button_list" id="change_scene_visualization"><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_scene_visualization(this);" value="Instances"/><!---
|
|
---><input type="button" onclick="g_view.resource_view.change_scene_visualization(this);" value="Normals"/>
|
|
</div>`;
|
|
|
|
update_value_selection(document.getElementById('change_scene_visualization'), this.scene_visualization);
|
|
|
|
// Create visualization shader programs
|
|
this.scene_visualization_shader_programs = {};
|
|
{
|
|
var vert_code = `
|
|
uniform mat4 local_to_world;
|
|
uniform mat4 world_to_view;
|
|
uniform mat4 view_to_clip;
|
|
|
|
in vec3 vertex_coordinates;
|
|
out vec3 interpolator_world;
|
|
|
|
void main(void) {
|
|
vec4 world_pos = local_to_world * vec4(vertex_coordinates.xyz, 1.0);
|
|
vec4 view_pos = world_to_view * world_pos;
|
|
vec4 clip_pos = view_to_clip * view_pos;
|
|
|
|
gl_Position = clip_pos;
|
|
interpolator_world = world_pos.xyz;
|
|
}`;
|
|
|
|
var frag_normals_code = `
|
|
in vec3 interpolator_world;
|
|
out vec4 display;
|
|
|
|
void main(void) {
|
|
vec3 normal = normalize(cross(dFdx(interpolator_world), dFdy(interpolator_world)));
|
|
|
|
display = vec4(normal * 0.5 + 0.5, 1.0);
|
|
}`;
|
|
|
|
var frag_instances_code = `
|
|
uniform uint instance_index;
|
|
|
|
in vec3 interpolator_world;
|
|
out vec4 display;
|
|
|
|
uint MurmurMix(uint Hash)
|
|
{
|
|
Hash ^= Hash >> 16u;
|
|
Hash *= 0x85ebca6bu;
|
|
Hash ^= Hash >> 13u;
|
|
Hash *= 0xc2b2ae35u;
|
|
Hash ^= Hash >> 16u;
|
|
return Hash;
|
|
}
|
|
|
|
vec3 IntToColor(uint Index)
|
|
{
|
|
uint Hash = MurmurMix(Index);
|
|
|
|
vec3 Color = vec3
|
|
(
|
|
(Hash >> 0u) & 255u,
|
|
(Hash >> 8u) & 255u,
|
|
(Hash >> 16u) & 255u
|
|
);
|
|
|
|
return Color * (1.0f / 255.0f);
|
|
}
|
|
|
|
void main(void) {
|
|
vec3 normal = normalize(cross(dFdx(interpolator_world), dFdy(interpolator_world)));
|
|
|
|
vec3 vert_color = IntToColor(1u + instance_index);
|
|
vec3 base_color = vert_color * pow(gl_FragCoord.w, 0.1);
|
|
|
|
display = vec4(mix(base_color, normal * 0.5 + 0.5, 1.0 / 3.0), 1.0);
|
|
}`;
|
|
|
|
this.scene_visualization_shader_programs['Normals'] = gl_create_shader_program(
|
|
this.gl, vert_code, frag_normals_code);
|
|
this.scene_visualization_shader_programs['Instances'] = gl_create_shader_program(
|
|
this.gl, vert_code, frag_instances_code);
|
|
}
|
|
|
|
var raytracing_view = this;
|
|
var subresource_version_name = get_subresource_unique_version_name(this.subresource_version_info);
|
|
|
|
load_resource_binary_file(`Resources/${subresource_version_name}.bin`, function(raw_raytracing_data)
|
|
{
|
|
if (raw_raytracing_data)
|
|
{
|
|
raytracing_view.parse_raw_raytracing_data(raw_raytracing_data);
|
|
raytracing_view.is_ready = true;
|
|
}
|
|
else
|
|
{
|
|
raytracing_view.error(`Error: Resources/${subresource_version_name}.bin couldn't be loaded.`);
|
|
}
|
|
});
|
|
|
|
document.title = `${g_view.pass_data['EventName']} - ${this.resource_desc['Name']}`;
|
|
}
|
|
|
|
parse_raw_raytracing_data(raw_raytracing_data)
|
|
{
|
|
var is_little_endian = true;
|
|
|
|
var data_view = new DataView(raw_raytracing_data);
|
|
var data_byte_offset = 0;
|
|
|
|
function set_byte_offset(byte_offset)
|
|
{
|
|
data_byte_offset = byte_offset;
|
|
}
|
|
|
|
function skip_bytes(byte_offset)
|
|
{
|
|
data_byte_offset += byte_offset;
|
|
}
|
|
|
|
function read_uint8()
|
|
{
|
|
var r = data_view.getUint8(data_byte_offset);
|
|
data_byte_offset += 1;
|
|
return r;
|
|
}
|
|
|
|
function read_uint32()
|
|
{
|
|
var r = data_view.getUint32(data_byte_offset, is_little_endian);
|
|
data_byte_offset += 4;
|
|
return r;
|
|
}
|
|
|
|
function read_uint64()
|
|
{
|
|
var l = BigInt(data_view.getUint32(data_byte_offset, is_little_endian));
|
|
var r = BigInt(data_view.getUint32(data_byte_offset + 4, is_little_endian));
|
|
data_byte_offset += 8;
|
|
return is_little_endian
|
|
? l + BigInt(2 ** 32) * r
|
|
: BigInt(2 ** 32) * l + r;
|
|
}
|
|
|
|
function read_uint32_array(size)
|
|
{
|
|
var r = new Array(size);
|
|
for (var i = 0; i < size; i++)
|
|
{
|
|
r[i] = data_view.getUint32(data_byte_offset, is_little_endian);
|
|
data_byte_offset += 4;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
function read_float32_array(size)
|
|
{
|
|
var r = new Array(size);
|
|
for (var i = 0; i < size; i++)
|
|
{
|
|
r[i] = data_view.getFloat32(data_byte_offset, is_little_endian);
|
|
data_byte_offset += 4;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
function assert_magic(magic)
|
|
{
|
|
var m = read_uint64();
|
|
if (magic !== m)
|
|
{
|
|
throw new Error(`Magic assertion ${magic} failed`);
|
|
}
|
|
}
|
|
|
|
const MaxNumLayers = 64;
|
|
|
|
// UE::HWRTScene::FSceneHeader
|
|
assert_magic(0x72ffa3376f48683bn);
|
|
assert_magic(1n);
|
|
var Offsets_Instances = read_uint32();
|
|
var Offsets_Geometries = read_uint32();
|
|
var Offsets_Buffers = read_uint32();
|
|
var Offsets_Strings = read_uint32();
|
|
var NumLayers = read_uint32();
|
|
var PerLayerNumInstances = read_uint32_array(MaxNumLayers);
|
|
var NumInstances = read_uint32();
|
|
var NumGeometries = read_uint32();
|
|
var NumBuffers = read_uint32();
|
|
var NumStrings = read_uint32();
|
|
skip_bytes(4 * 2);
|
|
|
|
set_byte_offset(Offsets_Instances);
|
|
var Instances = [];
|
|
for (var i = 0; i < NumInstances; i++)
|
|
{
|
|
var NewInstance = {};
|
|
Instances.push(NewInstance);
|
|
|
|
set_byte_offset(Offsets_Instances + i * (3 * 4 * 4 + 4 * 2 + 8));
|
|
|
|
// UE::HWRTScene::FInstanceDesc
|
|
NewInstance.Transform = read_float32_array(3 * 4);
|
|
var A = read_uint32();
|
|
var B = read_uint32();
|
|
NewInstance.InstanceID = (A >> 0) & 0xFFFFFF;
|
|
NewInstance.InstanceMask = (A >> 24) & 0xFF;
|
|
NewInstance.InstanceContributionToHitGroupIndex = (B >> 0) & 0xFFFFFF;
|
|
NewInstance.Flags = (B >> 24) & 0xFF;
|
|
NewInstance.AccelerationStructure = read_uint64();
|
|
}
|
|
|
|
// UE::HWRTScene::FGeometryHeader
|
|
set_byte_offset(Offsets_Geometries);
|
|
var Geometries = [];
|
|
for (var GeometryIndex = 0; GeometryIndex < NumGeometries; GeometryIndex++)
|
|
{
|
|
var Geometry = {};
|
|
Geometries.push(Geometry);
|
|
|
|
assert_magic(0x47226e42ad539683n);
|
|
Geometry.IndexBuffer = read_uint32();
|
|
Geometry.NumSegments = read_uint32();
|
|
skip_bytes(4 + 4 + 8);
|
|
|
|
// UE::HWRTScene::FSegmentHeader
|
|
Geometry.Segments = [];
|
|
for (var SegmentIndex = 0; SegmentIndex < Geometry.NumSegments; SegmentIndex++)
|
|
{
|
|
var Segment = {};
|
|
Geometry.Segments.push(Segment);
|
|
|
|
Segment.VertexBuffer = read_uint32();
|
|
Segment.VertexType = read_uint32();
|
|
Segment.VertexBufferOffset = read_uint32();
|
|
Segment.VertexBufferStride = read_uint32();
|
|
Segment.MaxVertices = read_uint32();
|
|
Segment.FirstPrimitive = read_uint32();
|
|
Segment.NumPrimitives = read_uint32();
|
|
|
|
Segment.bForceOpaque = read_uint8();
|
|
Segment.bAllowDuplicateAnyHitShaderInvocation = read_uint8();
|
|
Segment.bEnabled = read_uint8();
|
|
skip_bytes(1);
|
|
}
|
|
}
|
|
|
|
// UE::HWRTScene::FBufferHeader
|
|
set_byte_offset(Offsets_Buffers);
|
|
var RawBuffers = [];
|
|
for (var BufferIndex = 0; BufferIndex < NumBuffers; BufferIndex++)
|
|
{
|
|
var RawBuffer = {};
|
|
RawBuffers.push(RawBuffer);
|
|
|
|
assert_magic(0x7330d54d0195a6den);
|
|
RawBuffer.SizeInBytes = read_uint32();
|
|
RawBuffer.StrideInBytes = read_uint32();
|
|
RawBuffer.ByteOffset = data_byte_offset;
|
|
skip_bytes(RawBuffer.SizeInBytes);
|
|
}
|
|
|
|
// Translate UE::HWRTScene to webgl draw calls
|
|
var webgl_buffers = new Array(RawBuffers.length);
|
|
var min_position = [0, 0, 0];
|
|
var max_position = [0, 0, 0];
|
|
var gl = this.gl;
|
|
for (var instance_index = 0; instance_index < NumInstances; instance_index++)
|
|
{
|
|
var Instance = Instances[instance_index];
|
|
var Geometry = Geometries[Instance.AccelerationStructure];
|
|
var IndexBuffer = RawBuffers[Geometry.IndexBuffer];
|
|
|
|
// only support indexed geometry right now
|
|
if (IndexBuffer.SizeInBytes == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Upload index buffer to GPU if not already.
|
|
if (!webgl_buffers[Geometry.IndexBuffer])
|
|
{
|
|
var indices = null;
|
|
if (IndexBuffer.StrideInBytes == 4)
|
|
{
|
|
indices = new Uint32Array(raw_raytracing_data, IndexBuffer.ByteOffset, IndexBuffer.SizeInBytes / 4);
|
|
}
|
|
else if (IndexBuffer.StrideInBytes == 2)
|
|
{
|
|
indices = new Uint16Array(raw_raytracing_data, IndexBuffer.ByteOffset, IndexBuffer.SizeInBytes / 2);
|
|
}
|
|
else if (IndexBuffer.StrideInBytes == 1)
|
|
{
|
|
indices = new Uint8Array(raw_raytracing_data, IndexBuffer.ByteOffset, IndexBuffer.SizeInBytes / 1);
|
|
}
|
|
else
|
|
{
|
|
throw Error(`Unknown IndexBuffer.StrideInBytes=${IndexBuffer.StrideInBytes}`);
|
|
}
|
|
console.assert(indices.byteOffset == IndexBuffer.ByteOffset);
|
|
console.assert(indices.byteLength == IndexBuffer.SizeInBytes);
|
|
|
|
webgl_buffers[Geometry.IndexBuffer] = gl_create_index_buffer(gl, indices);
|
|
}
|
|
|
|
// setup local to world
|
|
var local_to_world = create_identity_matrix();
|
|
{
|
|
local_to_world[0 + 0 * 4] = Instance.Transform[0 * 4 + 0];
|
|
local_to_world[1 + 0 * 4] = Instance.Transform[1 * 4 + 0];
|
|
local_to_world[2 + 0 * 4] = Instance.Transform[2 * 4 + 0];
|
|
local_to_world[0 + 1 * 4] = Instance.Transform[0 * 4 + 1];
|
|
local_to_world[1 + 1 * 4] = Instance.Transform[1 * 4 + 1];
|
|
local_to_world[2 + 1 * 4] = Instance.Transform[2 * 4 + 1];
|
|
local_to_world[0 + 2 * 4] = Instance.Transform[0 * 4 + 2];
|
|
local_to_world[1 + 2 * 4] = Instance.Transform[1 * 4 + 2];
|
|
local_to_world[2 + 2 * 4] = Instance.Transform[2 * 4 + 2];
|
|
local_to_world[0 + 3 * 4] = Instance.Transform[0 * 4 + 3];
|
|
local_to_world[1 + 3 * 4] = Instance.Transform[1 * 4 + 3];
|
|
local_to_world[2 + 3 * 4] = Instance.Transform[2 * 4 + 3];
|
|
}
|
|
|
|
for (var SegmentIndex = 0; SegmentIndex < Geometry.NumSegments; SegmentIndex++)
|
|
{
|
|
var Segment = Geometry.Segments[SegmentIndex];
|
|
var VertexBuffer = RawBuffers[Segment.VertexBuffer];
|
|
|
|
console.assert(Segment.VertexType == 3); // VET_Float3
|
|
|
|
// Upload vertex buffer to GPU if not already.
|
|
if (!webgl_buffers[Segment.VertexBuffer])
|
|
{
|
|
var vertices = new Float32Array(raw_raytracing_data, VertexBuffer.ByteOffset, VertexBuffer.SizeInBytes / 4);
|
|
console.assert(vertices.byteOffset == VertexBuffer.ByteOffset);
|
|
console.assert(vertices.byteLength == VertexBuffer.SizeInBytes);
|
|
webgl_buffers[Segment.VertexBuffer] = gl_create_vertex_buffer(gl, vertices);
|
|
}
|
|
|
|
var draw_call = {};
|
|
this.draw_calls.push(draw_call);
|
|
|
|
draw_call.local_to_world = local_to_world;
|
|
draw_call.instance_index = instance_index;
|
|
if (IndexBuffer.StrideInBytes == 4)
|
|
{
|
|
draw_call.index_type = gl.UNSIGNED_INT;
|
|
}
|
|
else if (IndexBuffer.StrideInBytes == 2)
|
|
{
|
|
draw_call.index_type = gl.UNSIGNED_SHORT;
|
|
}
|
|
else if (IndexBuffer.StrideInBytes == 1)
|
|
{
|
|
draw_call.index_type = gl.UNSIGNED_BYTE;
|
|
}
|
|
else
|
|
{
|
|
throw Error(`Unknown IndexBuffer.StrideInBytes=${IndexBuffer.StrideInBytes}`);
|
|
}
|
|
draw_call.index_byte_offset = Segment.FirstPrimitive * 3 * IndexBuffer.StrideInBytes;
|
|
draw_call.index_count = Segment.NumPrimitives * 3;
|
|
|
|
// create vertex array.
|
|
{
|
|
console.assert(Segment.VertexBufferStride == 12);
|
|
console.assert((Segment.VertexBufferOffset % Segment.VertexBufferStride) == 0);
|
|
|
|
draw_call.vertex_array = gl.createVertexArray();
|
|
gl.bindVertexArray(draw_call.vertex_array);
|
|
gl.bindBuffer(gl.ARRAY_BUFFER, webgl_buffers[Segment.VertexBuffer]);
|
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, webgl_buffers[Geometry.IndexBuffer]);
|
|
gl.enableVertexAttribArray(0);
|
|
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, Segment.VertexBufferStride, Segment.VertexBufferOffset);
|
|
gl.bindVertexArray(null);
|
|
console.assert(gl.getError() == gl.NO_ERROR);
|
|
}
|
|
|
|
// Track the min and max instance position for seting up the camera over the entire scene.
|
|
if (instance_index == 0)
|
|
{
|
|
min_position[0] = Instance.Transform[0 * 4 + 3];
|
|
min_position[1] = Instance.Transform[1 * 4 + 3];
|
|
min_position[2] = Instance.Transform[2 * 4 + 3];
|
|
|
|
max_position[0] = Instance.Transform[0 * 4 + 3];
|
|
max_position[1] = Instance.Transform[1 * 4 + 3];
|
|
max_position[2] = Instance.Transform[2 * 4 + 3];
|
|
}
|
|
else
|
|
{
|
|
min_position[0] = Math.min(min_position[0], Instance.Transform[0 * 4 + 3]);
|
|
min_position[1] = Math.min(min_position[0], Instance.Transform[1 * 4 + 3]);
|
|
min_position[2] = Math.min(min_position[0], Instance.Transform[2 * 4 + 3]);
|
|
|
|
max_position[0] = Math.max(max_position[0], Instance.Transform[0 * 4 + 3]);
|
|
max_position[1] = Math.max(max_position[0], Instance.Transform[1 * 4 + 3]);
|
|
max_position[2] = Math.max(max_position[0], Instance.Transform[2 * 4 + 3]);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (true)
|
|
{
|
|
this.camera_pos = max_position;
|
|
this.camera_look_at([(min_position[0] + max_position[0]) / 2, (min_position[1] + max_position[1]) / 2, (min_position[2] + max_position[2]) / 2]);
|
|
}
|
|
|
|
this.draw_canvas();
|
|
} // parse_raw_raytracing_data()
|
|
|
|
change_scene_visualization(new_scene_visualization)
|
|
{
|
|
if (!this.is_ready)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (new_scene_visualization.value == this.scene_visualization)
|
|
{
|
|
return;
|
|
}
|
|
|
|
this.scene_visualization = new_scene_visualization.value;
|
|
update_value_selection(document.getElementById('change_scene_visualization'), this.scene_visualization);
|
|
this.draw_canvas();
|
|
}
|
|
|
|
draw_canvas()
|
|
{
|
|
var gl = this.gl;
|
|
|
|
var world_to_view = this.get_view_matrix();
|
|
var view_to_clip = this.get_proj_matrix();
|
|
|
|
// view_matrix[0 + 0 * 4] = 0.5;
|
|
// view_matrix[0 + 1 * 4] = 0.5;
|
|
// view_matrix[1 + 1 * 4] = 0.5;
|
|
// view_matrix[2 + 2 * 4] = 0.5;
|
|
|
|
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
|
|
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
|
|
gl.clearDepth(0.0);
|
|
gl.clear(gl.DEPTH_BUFFER_BIT);
|
|
|
|
gl.enable(gl.DEPTH_TEST);
|
|
gl.depthFunc(gl.GEQUAL);
|
|
|
|
gl.disable(gl.CULL_FACE);
|
|
gl.cullFace(gl.FRONT_AND_BACK);
|
|
|
|
{
|
|
var shader_program = this.scene_visualization_shader_programs[this.scene_visualization];
|
|
gl.useProgram(shader_program);
|
|
|
|
var coord = gl.getAttribLocation(shader_program, "vertex_coordinates");
|
|
console.assert(coord == 0);
|
|
|
|
gl_set_uniform_mat4(gl, shader_program, 'local_to_world', create_identity_matrix());
|
|
gl_set_uniform_mat4(gl, shader_program, 'world_to_view', world_to_view);
|
|
gl_set_uniform_mat4(gl, shader_program, 'view_to_clip', view_to_clip);
|
|
|
|
for (var draw_call of this.draw_calls)
|
|
{
|
|
gl_set_uniform_mat4(gl, shader_program, 'local_to_world', draw_call.local_to_world);
|
|
gl_set_uniform_uint(gl, shader_program, 'instance_index', draw_call.instance_index);
|
|
|
|
gl.bindVertexArray(draw_call.vertex_array);
|
|
gl.drawElementsInstanced(
|
|
gl.TRIANGLES,
|
|
/* index_count = */ draw_call.index_count,
|
|
/* index_type = */ draw_call.index_type,
|
|
/* index_byte_offset = */ draw_call.index_byte_offset,
|
|
/* instanceCount = */ 1);
|
|
}
|
|
|
|
gl.useProgram(null);
|
|
gl.bindVertexArray(null);
|
|
}
|
|
}
|
|
|
|
release()
|
|
{
|
|
super.release();
|
|
this.draw_calls = []
|
|
this.scene_visualization_shader_programs = {};
|
|
}
|
|
} // class RaytracingAccelerationStructureView
|
|
|
|
|
|
// -------------------------------------------------------------------- UI
|
|
|
|
function update_href_selection(parent)
|
|
{
|
|
var all_a = parent.getElementsByTagName("a");
|
|
|
|
var navs = [location.hash.substring(1)];
|
|
if (g_view)
|
|
{
|
|
navs = navs.concat(g_view.navigations);
|
|
}
|
|
|
|
for (let a of all_a)
|
|
{
|
|
var href = a.href.split('#');
|
|
|
|
if (navs.includes(`${href[1]}`))
|
|
{
|
|
a.classList.add('match_location_hash');
|
|
}
|
|
else if (a.classList.contains('match_location_hash'))
|
|
{
|
|
a.classList.remove('match_location_hash');
|
|
}
|
|
};
|
|
}
|
|
|
|
function update_value_selection(parent, selected_value)
|
|
{
|
|
var all_a = parent.getElementsByTagName("input");
|
|
|
|
for (let a of all_a)
|
|
{
|
|
if (a.value == selected_value)
|
|
{
|
|
a.classList.add('match_value');
|
|
}
|
|
else if (a.classList.contains('match_value'))
|
|
{
|
|
a.classList.remove('match_value');
|
|
}
|
|
};
|
|
}
|
|
|
|
function render_selection_list_html(parent_dom, display_list, options)
|
|
{
|
|
if ('deduplicate' in options)
|
|
{
|
|
var href_set = new Set();
|
|
var new_display_list = [];
|
|
for (const display_list_item of display_list)
|
|
{
|
|
if (display_list_item['href'])
|
|
{
|
|
if (!href_set.has(display_list_item['href']))
|
|
{
|
|
href_set.add(display_list_item['href']);
|
|
new_display_list.push(display_list_item);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
new_display_list.push(display_list_item);
|
|
}
|
|
}
|
|
display_list = new_display_list;
|
|
}
|
|
|
|
if ('sort' in options)
|
|
{
|
|
display_list.sort(function(a, b)
|
|
{
|
|
if (a['name'] < b['name'])
|
|
{
|
|
return -1;
|
|
}
|
|
else if (a['name'] > b['name'])
|
|
{
|
|
return 1;
|
|
}
|
|
return 0;
|
|
});
|
|
}
|
|
|
|
var search = '';
|
|
if ('search' in options)
|
|
{
|
|
search = options['search'];
|
|
}
|
|
|
|
var html = '';
|
|
for (const display_list_item of display_list)
|
|
{
|
|
if (search && !display_list_item['name'].toLowerCase().includes(search.toLowerCase()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (display_list_item['href'])
|
|
{
|
|
html += `<a href="${display_list_item['href']}">${display_list_item['name']}</a>`;
|
|
}
|
|
else
|
|
{
|
|
html += `<a>${display_list_item['name']}</a>`;
|
|
}
|
|
}
|
|
|
|
if (html == '')
|
|
{
|
|
if (search)
|
|
{
|
|
html += `<div class="empty">No matches for "${search}"</div>`;
|
|
}
|
|
else
|
|
{
|
|
html += `<div class="empty">None</div>`;
|
|
}
|
|
}
|
|
|
|
parent_dom.innerHTML = html;
|
|
update_href_selection(parent_dom);
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- SWITCH TAB IN BROWSER
|
|
|
|
document.addEventListener("visibilitychange", function() {
|
|
if (document.hidden)
|
|
{
|
|
// NOP
|
|
}
|
|
else
|
|
{
|
|
// apply the last zoom setting from last used tab when switting back to this tab.
|
|
if (g_view && 'resource_view' in g_view && g_view.resource_view instanceof TextureView)
|
|
{
|
|
var zoom_settings = get_local_storage(k_texture_zoom_local_storage);
|
|
if (zoom_settings && g_view.resource_view.is_zoom_settings_compatible(zoom_settings))
|
|
{
|
|
g_view.resource_view.apply_zoom_settings(zoom_settings);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
|
|
// -------------------------------------------------------------------- WINDOW LAYOUT
|
|
|
|
function init_top()
|
|
{
|
|
document.getElementById('top_bar').innerHTML = `${g_infos['Project']} - ${g_infos['Platform']} - ${g_infos['RHI']} - ${g_infos['BuildVersion']} - ${g_infos['DumpTime']} `;
|
|
}
|
|
|
|
function init_top_buttons()
|
|
{
|
|
document.getElementById('top_buttons').innerHTML = `
|
|
<a href="#display_infos();">Infos</a>
|
|
<a href="#display_cvars();">CVars</a>
|
|
<a href="#display_log();">Log</a>`;
|
|
document.getElementById('top_viewer_buttons').innerHTML = `
|
|
<a id="console_button" href="#display_console();">Console</a>`;
|
|
update_console_button();
|
|
}
|
|
|
|
function init_page()
|
|
{
|
|
// Load the dump service information to know how to read files. This is independent of Base/Infos.json because may depends whether it was uploaded through the DumpGPUServices plugin.
|
|
g_dump_service = load_json('Base/DumpService.json');
|
|
|
|
// Load the base information of the page.
|
|
g_infos = load_json('Base/Infos.json');
|
|
|
|
if (!g_dump_service || !g_infos)
|
|
{
|
|
document.body.innerHTML = 'Please use OpenGPUDumpViewer script for the viewer to work correctly.';
|
|
return;
|
|
}
|
|
|
|
// Verify the status of the dump, to check whether a crash happened during the dump.
|
|
{
|
|
var dump_status = load_text_file('Base/Status.txt');
|
|
if (dump_status == 'ok')
|
|
{
|
|
add_console_event('log', `The dump completed gracefully.`);
|
|
}
|
|
else if (dump_status == 'crash')
|
|
{
|
|
add_console_event('error', `A crash happened while the frame was being dumped.`);
|
|
}
|
|
else if (dump_status == 'dumping')
|
|
{
|
|
add_console_event('error', `The dump has not completed. This may be due to opening the viewer before completion, or serious problem that has not been handled with FCoreDelegates::OnShutdownAfterError`);
|
|
}
|
|
else
|
|
{
|
|
add_console_event('error', `The dump completed with status: ${dump_status}`);
|
|
}
|
|
}
|
|
|
|
// Load the cvars used for dumping.
|
|
{
|
|
g_dump_cvars = {};
|
|
|
|
var cvars_csv = load_text_file('Base/ConsoleVariables.csv');
|
|
var csv_lines = cvars_csv.split('\n');
|
|
for (var i = 1; i < csv_lines.length - 1; i++)
|
|
{
|
|
var csv_line = csv_lines[i].split(',');
|
|
var entry = {};
|
|
entry['name'] = csv_line[0];
|
|
if (entry['name'].startsWith('r.DumpGPU.'))
|
|
{
|
|
entry['type'] = csv_line[1];
|
|
entry['set_by'] = csv_line[2];
|
|
entry['value'] = csv_line[3];
|
|
g_dump_cvars[entry['name']] = entry['value'];
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Load all the passes.
|
|
g_passes = load_json_dict_sequence('Base/Passes.json');
|
|
{
|
|
for (var pass_id = 0; pass_id < g_passes.length; pass_id++)
|
|
{
|
|
g_passes[pass_id]['DrawCount'] = 0;
|
|
if (!g_passes[pass_id]['EventName'])
|
|
{
|
|
g_passes[pass_id]['EventName'] = 'Unnamed pass';
|
|
}
|
|
}
|
|
|
|
var pass_draw_counts = load_json_dict_sequence('Base/PassDrawCounts.json');
|
|
if (pass_draw_counts)
|
|
{
|
|
pass_draw_counts.forEach(function(pass_draw_count_data) {
|
|
for (var pass_id = 0; pass_id < g_passes.length; pass_id++)
|
|
{
|
|
if (g_passes[pass_id]['Pointer'] == pass_draw_count_data['Pointer'])
|
|
{
|
|
g_passes[pass_id]['DrawCount'] = pass_draw_count_data['DrawCount'];
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Load all the resource descriptors
|
|
{
|
|
g_descs = {};
|
|
|
|
var desc_list = load_json_dict_sequence('Base/ResourceDescs.json');
|
|
for (const desc of desc_list)
|
|
{
|
|
g_descs[desc['UniqueResourceName']] = desc;
|
|
}
|
|
}
|
|
|
|
if (!does_file_exists(get_log_path()))
|
|
{
|
|
add_console_event('error', `The dump does not contain ${get_log_path()}. The log is normally copied into the dump directory once the dump is completed. Failing to have is may be due to a premature end of the dumping process.`);
|
|
}
|
|
else
|
|
{
|
|
add_console_event('log', `Viewer init ok`);
|
|
}
|
|
|
|
// Analyses the capture.
|
|
{
|
|
analyses_passes();
|
|
}
|
|
|
|
init_top();
|
|
init_top_buttons();
|
|
display_pass_hierarchy();
|
|
|
|
// Find the last passes that have a displayable texture 2D.
|
|
if (location.hash)
|
|
{
|
|
return navigate_to_hash();
|
|
}
|
|
|
|
// Find the last pass that modify the RDG resource set by 'r.DumpGPU.Viewer.Visualize'
|
|
if ('r.DumpGPU.Viewer.Visualize' in g_dump_cvars && g_dump_cvars['r.DumpGPU.Viewer.Visualize'] != '')
|
|
{
|
|
var pass_id_to_open = -1;
|
|
var subresource_unique_name_to_open = null;
|
|
|
|
document.getElementById('resource_search_input').value = g_dump_cvars['r.DumpGPU.Viewer.Visualize'];
|
|
display_pass_hierarchy();
|
|
|
|
for (var pass_id = 0; pass_id < g_passes.length && pass_id_to_open == -1; pass_id++)
|
|
{
|
|
var pass_data = g_passes[g_passes.length - 1 - pass_id];
|
|
|
|
for (var subresource_unique_name of pass_data['OutputResources'])
|
|
{
|
|
var subresource_info = parse_subresource_unique_name(subresource_unique_name);
|
|
var resource_desc = get_resource_desc(subresource_info['resource']);
|
|
|
|
if (resource_desc === null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (resource_desc['Name'] == g_dump_cvars['r.DumpGPU.Viewer.Visualize'] && pass_id_to_open == -1)
|
|
{
|
|
pass_id_to_open = g_passes.length - 1 - pass_id;
|
|
subresource_unique_name_to_open = subresource_unique_name;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pass_id_to_open != -1)
|
|
{
|
|
return redirect_to_hash(`display_output_resource(${pass_id_to_open},'${subresource_unique_name_to_open}');`);
|
|
}
|
|
}
|
|
|
|
return display_tip();
|
|
}
|
|
|
|
function onresize_body()
|
|
{
|
|
var window_width = window.innerWidth;
|
|
var window_height = window.innerHeight;
|
|
|
|
const top_h = 45;
|
|
|
|
document.getElementById('onresize_body_table').style.width = `${window_width}px`;
|
|
document.getElementById('onresize_body_table').style.height = `${window_height}px`;
|
|
|
|
const top_height = 45;
|
|
|
|
var body_left_width = Math.floor(window_width * 0.25);
|
|
var body_right_width = window_width - body_left_width;
|
|
var body_height = window_height - top_height;
|
|
{
|
|
document.getElementById('onresize_body_top_bar').style.width = `${window_width}px`;
|
|
document.getElementById('onresize_body_top_bar').style.height = `${top_height}px`;
|
|
document.getElementById('onresize_body_top_bar').style.overflow = `hidden`;
|
|
|
|
document.getElementById('onresize_body_left').style.width = `${body_left_width}px`;
|
|
document.getElementById('onresize_body_left').style.height = `${body_height}px`;
|
|
|
|
document.getElementById('onresize_body_right').style.width = `${body_right_width}px`;
|
|
document.getElementById('onresize_body_right').style.height = `${body_height}px`;
|
|
}
|
|
|
|
{
|
|
const search_h = (30 + 2 * 5) * 2;
|
|
|
|
document.getElementById('pass_hierarchy').style.width = `${body_left_width - k_style_padding_size * 2}px`;
|
|
document.getElementById('pass_hierarchy').style.height = `${body_height - search_h - k_style_padding_size * 2 - k_style_scroll_width}px`;
|
|
document.getElementById('pass_hierarchy').style.overflow = `scroll`;
|
|
}
|
|
|
|
{
|
|
document.getElementById('main_right_pannel').style.width = `${body_right_width}px`;
|
|
document.getElementById('main_right_pannel').style.height = `${body_height}px`;
|
|
document.getElementById('main_right_pannel').style.overflow = `scroll`;
|
|
}
|
|
|
|
if (g_view !== null)
|
|
{
|
|
var ctx = {};
|
|
ctx.width = body_right_width - k_style_scroll_width;
|
|
ctx.height = body_height - k_style_scroll_width;
|
|
g_view.resize(ctx);
|
|
}
|
|
}
|
|
|
|
|
|
// -------------------------------------------------------------------- NAVIGATION
|
|
|
|
var g_previous_location_hash = null;
|
|
|
|
function redirect_to_hash(new_hash)
|
|
{
|
|
if (new_hash === g_previous_location_hash)
|
|
{
|
|
return;
|
|
}
|
|
|
|
location.hash = new_hash;
|
|
}
|
|
|
|
function navigate_to_hash()
|
|
{
|
|
if (location.hash === g_previous_location_hash)
|
|
{
|
|
return;
|
|
}
|
|
|
|
g_previous_location_hash = location.hash;
|
|
|
|
if (location.hash)
|
|
{
|
|
var js = location.hash.substring(1);
|
|
eval(js);
|
|
}
|
|
else
|
|
{
|
|
display_tip();
|
|
}
|
|
|
|
update_href_selection(document);
|
|
}
|
|
|
|
function get_current_navigation()
|
|
{
|
|
return g_previous_location_hash;
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
<style type="text/css">
|
|
|
|
|
|
/* ----------------------------------------------------- them colors */
|
|
|
|
:root
|
|
{
|
|
--border-radius: 2px;
|
|
|
|
--body-bg-color: rgb(21, 21, 21);
|
|
--body-txt-size: 12px;
|
|
|
|
--div-bg-color: rgb(36, 36, 36);
|
|
--div-txt-color: rgb(192, 192, 192);
|
|
--a-bg-color-odd: rgb(26, 26, 26);
|
|
--a-bg-color-hover: #332222;
|
|
--a-bg-color-hoverhref: rgb(38, 67, 81);
|
|
--a-txt-color-disabled: rgb(113, 113, 113);
|
|
|
|
--a-bg-color-selected: rgb(38, 187, 255);
|
|
--a-txt-color-selected: rgb(15, 15, 15);
|
|
|
|
--channel-red-color: rgb(255, 38, 38);
|
|
--channel-green-color: rgb(38, 255, 38);
|
|
--channel-blue-color: rgb(38, 187, 255);
|
|
--channel-alpha-color: white;
|
|
|
|
--error-txt-color: var(--channel-red-color);
|
|
|
|
--header-bg-color: rgb(47, 47, 47);
|
|
|
|
--button-border-radius: var(--border-radius);
|
|
--button-bg-color: rgb(56, 56, 56);
|
|
--button-bg-color-hover: rgb(87, 87, 87);
|
|
--button-bg-color-selected: rgb(15, 15, 15);
|
|
--button-txt-color: var(--div-txt-color);
|
|
--button-txt-color-hover: rgb(255, 255, 255);
|
|
--button-txt-color-selected: rgb(38, 187, 255);
|
|
|
|
|
|
--scroll-bg-color: var(--a-bg-color-odd);
|
|
--scroll-color: rgb(87, 87, 87);
|
|
--scroll-color-hover: rgb(128, 128, 128);
|
|
|
|
--input-bg-color: rgb(15, 15, 15);
|
|
--input-border: 1px solid rgb(55, 55, 55);
|
|
--input-border-hover: rgb(83, 83, 83);
|
|
--input-border-focus: rgb(38, 176, 239);
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- override default */
|
|
|
|
body
|
|
{
|
|
overflow: hidden;
|
|
background: var(--body-bg-color);
|
|
color: var(--div-txt-color);
|
|
margin: 0;
|
|
padding: 0;
|
|
/*font-family: Arial, Helvetica, sans-serif;*/
|
|
font-family: consolas, "Liberation Mono", courier, monospace;
|
|
font-size: var(--body-txt-size);
|
|
/* overflow: hidden; */
|
|
}
|
|
|
|
table, tr, td, div, a
|
|
{
|
|
margin: 0;
|
|
padding: 0;
|
|
border-spacing: 0;
|
|
border-collapse: collapse;
|
|
vertical-align: top;
|
|
cursor: default;
|
|
font-size: inherit;
|
|
}
|
|
|
|
td
|
|
{
|
|
cursor: default;
|
|
}
|
|
|
|
div, a
|
|
{
|
|
display: block;
|
|
cursor: default;
|
|
}
|
|
|
|
a, a:hover, a:visited
|
|
{
|
|
color: inherit;
|
|
font: inherit;
|
|
text-decoration: none;
|
|
cursor: default;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- external link */
|
|
|
|
a.external_link
|
|
{
|
|
color: inherit;
|
|
cursor: pointer;
|
|
}
|
|
|
|
a.external_link:hover
|
|
{
|
|
color: var(--button-txt-color-hover);
|
|
}
|
|
|
|
a.external_link:active
|
|
{
|
|
color: var(--button-txt-color-selected);
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- inputs scroll bars */
|
|
|
|
input
|
|
{
|
|
padding: 3px 5px;
|
|
border-radius: var(--border-radius);
|
|
}
|
|
|
|
input[type=search]
|
|
{
|
|
padding: 3px 13px;
|
|
border-radius: 20px;
|
|
}
|
|
|
|
textarea
|
|
{
|
|
padding: 3px 3px;
|
|
}
|
|
|
|
input, textarea
|
|
{
|
|
background-color: var(--input-bg-color);
|
|
color: var(--div-txt-color);
|
|
border: var(--input-border);
|
|
outline: none;
|
|
font-size: inherit;
|
|
font: inherit;
|
|
}
|
|
|
|
input[readonly], textarea[readonly]
|
|
{
|
|
color: var(--a-txt-color-disabled);
|
|
}
|
|
|
|
input:not([readonly]):hover,
|
|
textarea:not([readonly]):hover
|
|
{
|
|
border-color: var(--input-border-hover);
|
|
}
|
|
|
|
input[type=text]:not([readonly]):focus,
|
|
input[type=search]:not([readonly]):focus,
|
|
textarea:not([readonly]):focus
|
|
{
|
|
color: rgb(254, 254, 254);
|
|
border-color: var(--input-border-focus);
|
|
}
|
|
|
|
input:focus, textarea:focus
|
|
{
|
|
outline: none;
|
|
}
|
|
|
|
input::placeholder
|
|
{
|
|
color: rgb(76, 76, 76);
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- webkit scroll bars */
|
|
|
|
*::-webkit-scrollbar {
|
|
width: 8px;
|
|
height: 8px;
|
|
background: var(--scroll-bg-color);
|
|
}
|
|
|
|
*::-webkit-scrollbar-corner, *::-webkit-scrollbar-track {
|
|
background: var(--scroll-bg-color);
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb {
|
|
background-color: var(--scroll-color);
|
|
border-radius: 20px;
|
|
}
|
|
|
|
*::-webkit-scrollbar-thumb:hover {
|
|
background-color: var(--scroll-color-hover);
|
|
border-radius: 20px;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- common layout */
|
|
|
|
.main_div
|
|
{
|
|
background: var(--div-bg-color);
|
|
padding: 5px;
|
|
margin: 5px;
|
|
border-radius: 3px;
|
|
}
|
|
|
|
#main_right_pannel .main_div
|
|
{
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
#main_right_pannel .main_div:last-child
|
|
{
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- Selection list */
|
|
|
|
.selection_list_title
|
|
{
|
|
font-size: 20;
|
|
font-weight: bold;
|
|
padding: 5px 20px 10px 20px;
|
|
}
|
|
|
|
.selection_list_search
|
|
{
|
|
padding: 0px 5px;
|
|
height: 30px;
|
|
width: auto;
|
|
}
|
|
|
|
.selection_list_search input[type=search]
|
|
{
|
|
width: 100%;
|
|
}
|
|
|
|
.selection_list
|
|
{
|
|
max-height: inherit;
|
|
overflow-x: auto;
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
.selection_list a
|
|
{
|
|
width: auto;
|
|
padding: 5px 20px;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.selection_list a:nth-child(odd)
|
|
{
|
|
background: var(--a-bg-color-odd);
|
|
}
|
|
|
|
.selection_list a:not(.match_location_hash):hover
|
|
{
|
|
background: var(--a-bg-color-hover);
|
|
}
|
|
|
|
.selection_list a:not(.match_location_hash)[href]:hover
|
|
{
|
|
background: var(--a-bg-color-hoverhref);
|
|
color: var(--button-txt-color-hover);
|
|
cursor: pointer;
|
|
}
|
|
|
|
/*.selection_list a.match_location_hash
|
|
{
|
|
background: var(--a-bg-color-selected);
|
|
color: var(--a-txt-color-selected);
|
|
}*/
|
|
|
|
.selection_list a.match_location_hash
|
|
{
|
|
background: var(--button-bg-color-selected);
|
|
color: var(--button-txt-color-selected);
|
|
}
|
|
|
|
.selection_list a.disabled
|
|
{
|
|
color: var(--a-txt-color-disabled);
|
|
}
|
|
|
|
.selection_list div.empty
|
|
{
|
|
width: 100%;
|
|
margin: 20px 0 0 0;
|
|
color: var(--a-txt-color-disabled);
|
|
text-align: center;
|
|
}
|
|
|
|
#main_right_pannel .selection_list
|
|
{
|
|
min-height: 100px;
|
|
max-height: 200px;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- table */
|
|
|
|
.pretty_table tr.header
|
|
{
|
|
background: var(--header-bg-color);
|
|
font-weight: bold;
|
|
}
|
|
|
|
.pretty_table tr:not(.header):nth-child(odd) td
|
|
{
|
|
background: var(--a-bg-color-odd);
|
|
}
|
|
|
|
.pretty_table tr:not(.header):hover td:not(.empty)
|
|
{
|
|
background: var(--a-bg-color-hover);
|
|
}
|
|
|
|
.pretty_table tr td
|
|
{
|
|
display: table-cell;
|
|
padding: 5px 20px;
|
|
}
|
|
|
|
.pretty_table tr td:first-child
|
|
{
|
|
width: 150px;
|
|
}
|
|
|
|
.pretty_table tr.header td
|
|
{
|
|
padding: 5px 30px;
|
|
}
|
|
|
|
.pretty_table tr.header td:not(:first-child)
|
|
{
|
|
border-left: 1px solid rgb(36, 36, 36);
|
|
}
|
|
|
|
.pretty_table .empty
|
|
{
|
|
padding: 20px 0 0 0;
|
|
color: var(--a-txt-color-disabled);
|
|
text-align: center;
|
|
}
|
|
|
|
.pretty_table tr.error td
|
|
{
|
|
color: var(--error-txt-color);
|
|
}
|
|
|
|
.resource_desc tr td:not(.empty)
|
|
{
|
|
width: 150px;
|
|
text-align: right;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- button */
|
|
|
|
.button_list a,
|
|
.button_list input
|
|
{
|
|
margin: 0;
|
|
display: inline-block;
|
|
display: table-cell;
|
|
padding: 3px 10px;
|
|
background-color: var(--button-bg-color);
|
|
color: var(--button-txt-color);
|
|
border-radius: 0;
|
|
}
|
|
|
|
.button_list a[href],
|
|
.button_list input
|
|
{
|
|
cursor: pointer;
|
|
}
|
|
|
|
.button_list a:first-child,
|
|
.button_list input:first-child
|
|
{
|
|
border-top-left-radius: var(--button-border-radius);
|
|
border-bottom-left-radius: var(--button-border-radius);
|
|
}
|
|
|
|
.button_list a:last-child,
|
|
.button_list input:last-child
|
|
{
|
|
border-top-right-radius: var(--button-border-radius);
|
|
border-bottom-right-radius: var(--button-border-radius);
|
|
}
|
|
|
|
.button_list a[href]:not(.match_location_hash):not(:active):hover,
|
|
.button_list input:not(.match_value):not(:active):hover
|
|
{
|
|
background-color: var(--button-bg-color-hover);
|
|
}
|
|
|
|
.button_list a[href]:not(.match_location_hash):not(.error):not(:active):hover,
|
|
.button_list input:not(.match_value):not(:active):hover
|
|
{
|
|
color: var(--button-txt-color-hover);
|
|
}
|
|
|
|
.button_list a[href]:active,
|
|
.button_list a[href].match_location_hash,
|
|
.button_list input:active,
|
|
.button_list input.match_value
|
|
{
|
|
background-color: var(--button-bg-color-selected);
|
|
color: var(--button-txt-color-selected);
|
|
}
|
|
|
|
.button_list a[href].error,
|
|
.button_list a[href].error:active
|
|
{
|
|
color: var(--error-txt-color);
|
|
}
|
|
|
|
.button_list span
|
|
{
|
|
margin-left: 5px;
|
|
margin-right: 3px;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- top bar */
|
|
|
|
#top_bar
|
|
{
|
|
font-size: 20;
|
|
font-weight: bold;
|
|
padding: 10px;
|
|
display: inline-block;
|
|
}
|
|
|
|
#top_buttons,
|
|
#top_viewer_buttons
|
|
{
|
|
padding: 12px;
|
|
display: inline-block;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- main_right_pannel */
|
|
|
|
#main_right_pannel .pass_title
|
|
{
|
|
font-size: 20;
|
|
font-weight: bolder;
|
|
padding: 10px 40px;
|
|
}
|
|
|
|
#main_right_pannel .pass_title .button_list
|
|
{
|
|
font-size: var(--body-txt-size);
|
|
margin-top: 3px;
|
|
}
|
|
|
|
#cvars_pannel
|
|
{
|
|
height: auto;
|
|
max-height: none;
|
|
}
|
|
|
|
#cvars_pannel .pretty_table tr td:nth-child(2)
|
|
{
|
|
text-align: right;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- TextureView */
|
|
|
|
#canvas_outter
|
|
{
|
|
background-color: var(--input-bg-color);
|
|
color: var(--div-txt-color);
|
|
border: var(--input-border);
|
|
}
|
|
|
|
#canvas_outter:not(.selected):hover
|
|
{
|
|
border-color: var(--input-border-hover);
|
|
}
|
|
|
|
#canvas_outter.selected
|
|
{
|
|
border-color: var(--input-border-focus);
|
|
}
|
|
|
|
#canvas_outter #canvas_header
|
|
{
|
|
padding: 3px;
|
|
border-bottom: var(--input-border);
|
|
}
|
|
|
|
#canvas_outter #canvas_header .button_list
|
|
{
|
|
display: inline-block;
|
|
}
|
|
|
|
#canvas_outter #canvas_footer
|
|
{
|
|
padding: 3px 10px;
|
|
border-top: var(--input-border);
|
|
}
|
|
|
|
#canvas_outter #canvas_viewport
|
|
{
|
|
overflow: hidden;
|
|
background-image:
|
|
linear-gradient(45deg, #000 25%, transparent 25%),
|
|
linear-gradient(45deg, transparent 75%, #000 75%),
|
|
linear-gradient(45deg, transparent 75%, #000 75%),
|
|
linear-gradient(45deg, #000 25%, var(--input-bg-color) 25%);
|
|
background-size:16px 16px;
|
|
background-position:0 0, 0 0, -8px -8px, 8px 8px;
|
|
}
|
|
|
|
#canvas_outter .error_msg
|
|
{
|
|
width: 100%;
|
|
height: 100%;
|
|
text-align: center;
|
|
color: var(--error-txt-color);
|
|
}
|
|
|
|
#canvas_outter canvas
|
|
{
|
|
cursor: crosshair;
|
|
image-rendering: optimizeSpeed;
|
|
image-rendering: -moz-crisp-edges;
|
|
image-rendering: -webkit-optimize-contrast;
|
|
image-rendering: -o-crisp-edges;
|
|
image-rendering: pixelated;
|
|
-ms-interpolation-mode: nearest-neighbor;
|
|
}
|
|
|
|
#texture_visualization_code_input
|
|
{
|
|
min-width: 800px;
|
|
min-height: 200px;
|
|
display: block;
|
|
width: auto;
|
|
}
|
|
|
|
#texture_visualization_code_log
|
|
{
|
|
margin-top: 10px;
|
|
min-width: 800px;
|
|
min-height: 200px;
|
|
display: block;
|
|
width: auto;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------- BufferView */
|
|
|
|
#buffer_outter
|
|
{
|
|
background-color: var(--input-bg-color);
|
|
color: var(--div-txt-color);
|
|
border: var(--input-border);
|
|
}
|
|
|
|
#buffer_outter:not(.selected):hover
|
|
{
|
|
border-color: var(--input-border-hover);
|
|
}
|
|
|
|
#buffer_outter.selected
|
|
{
|
|
border-color: var(--input-border-focus);
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport_header
|
|
{
|
|
padding: 3px;
|
|
border-bottom: var(--input-border);
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport
|
|
{
|
|
overflow: hidden;
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table tr.header
|
|
{
|
|
position: sticky;
|
|
top: 0;
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table:not(.display_elem_using_rows) tr.header td:first-child
|
|
{
|
|
padding: 0 3px;
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table tr td:not(.empty)
|
|
{
|
|
padding: 3px 5px 0px 5px;
|
|
width: 60px;
|
|
height: 20px;
|
|
overflow: hidden;
|
|
text-align: right;
|
|
/*font-family: consolas, "Liberation Mono", courier, monospace;*/
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table.display_elem_using_rows tr td:not(.empty):first-child
|
|
{
|
|
text-align: left;
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table tr td:first-child
|
|
{
|
|
width: 100px;
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table tr td:last-child
|
|
{
|
|
width: auto;
|
|
}
|
|
|
|
#buffer_outter #buffer_viewport #buffer_content_table tr.highlighted
|
|
{
|
|
color: var(--button-txt-color-selected);
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
<body onload="init_console(); onresize_body(); init_page();" onresize="onresize_body();" onhashchange="navigate_to_hash();">
|
|
<table cellpadding="0" cellspacing="0" border="0" id="onresize_body_table">
|
|
<tr>
|
|
<td colspan="2" id="onresize_body_top_bar">
|
|
<div id="top_bar"></div>
|
|
<div id="top_buttons" class="button_list"></div>
|
|
<div id="top_viewer_buttons" class="button_list"></div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td valign="top" id="onresize_body_left">
|
|
<div class="main_div">
|
|
<div id="pass_hierarchy_search" style="padding: 5px; height: 70px;">
|
|
<input type="search" id="pass_search_input" oninput="display_pass_hierarchy();" onchange="display_pass_hierarchy();" style="width: 100%;" placeholder="Search pass..." />
|
|
<input type="search" id="resource_search_input" oninput="display_pass_hierarchy();" onchange="display_pass_hierarchy();" style="width: 100%; margin-top: 10px;" placeholder="Search resource..." />
|
|
</div>
|
|
<div id="pass_hierarchy" class="selection_list"></div>
|
|
</div>
|
|
</td>
|
|
<td valign="top" id="onresize_body_right">
|
|
<div id="main_right_pannel"></div>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>
|