AnKangCAVE51/Package/Windows/Engine/Extras/GPUDumpViewer/GPUDumpViewer.html

4874 lines
126 KiB
HTML
Raw Normal View History

2024-09-02 13:44:09 +08:00
<!-- Copyright Epic Games, Inc. All Rights Reserved. -->
<html>
<title>GPU Dump Viewer</title>
<script type="text/javascript">
// -------------------------------------------------------------------- CONSTANTS
const k_current_dir = "./";
const k_scroll_width = 8;
const k_null_json_ptr = "0000000000000000";
// -------------------------------------------------------------------- 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);
}
// -------------------------------------------------------------------- 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);
}
}
// -------------------------------------------------------------------- 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">
${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_input_resource_list();
this.refresh_output_resource_list();
this.refresh_draw_resource_list();
this.refresh_resource_modifying_list();
this.refresh_resource_reading_list();
}
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 = this.pass_data['OutputResources'].includes(get_subresource_unique_name(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')
{
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
if (g_view && 'resource_view' in g_view && g_view.resource_view instanceof TextureView)
{
var zoom_settings = g_view.resource_view.get_zoom_settings();
if (new_texture_view.is_zoom_settings_compatible(zoom_settings))
{
new_texture_view.onload = function() {
new_texture_view.apply_zoom_settings(zoom_settings);
};
}
}
g_view.set_resource_view(new_texture_view);
}
}
// -------------------------------------------------------------------- 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*".',
'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['Project']}.log`;
}
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">Log</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();`];
}
release()
{
this.log = null;
}
}
function display_log()
{
set_main_view(new LogView());
}
// -------------------------------------------------------------------- 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.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">
<input type="button" onclick="g_view.resource_view.copy_canvas_to_clipboard();" value="Copy to clipboard"/>
</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"/><!---
---><input type="button" onclick="g_view.resource_view.change_display_mode(this);" value="NaN"/><!---
---><input type="button" onclick="g_view.resource_view.change_display_mode(this);" value="Inf"/>
</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_display_modes'), `Visualization`);
// 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_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 + (extent['y'] - 1 - 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_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();
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;
}
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 - 100;
var viewport_width_no_scroll = viewport_width - k_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_scroll_width}px`;
canvas_viewport.style.height = `${viewport_width_no_scroll * canvas_rendering_res_y / canvas_rendering_res_x + k_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_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_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;
}
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();
}
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;
// 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 vertex buffer
{
var vertices = [
-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_vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.gl_vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
}
// Create vertex buffer
{
var indices = [0, 1, 2, 2, 0, 3];
this.gl_index_buffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.gl_index_buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
// 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 texture_view = this;
this.canvas.toBlob(function(image_blob) {
var pass_data = g_view.pass_data;
var is_output_resource = pass_data['OutputResources'].includes(get_subresource_unique_name(texture_view.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(texture_view.subresource_version_info, texture_view.resource_desc);
text += `${is_output_resource ? 'Output' : 'Input'} resource:\n\t${name}\n`;
}
if (texture_view.subresource_version_info['draw'] >= 0)
{
text += `Draw ${texture_view.subresource_version_info['draw']}:\n\t${g_view.pass_draws_data[texture_view.subresource_version_info['draw']]['DrawName']}\n`;
}
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');
}
get shader_code_saving_key()
{
var name = '';
if (this.resource_desc['Name'])
{
name = this.resource_desc['Name'];
}
else
{
name = this.subresource_version_info['resource'];
}
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')
{
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_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_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_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 gl = this.gl;
//console.log(frag_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
`;
var vert_code = `
in vec3 coordinates;
out highp vec2 uv;
void main(void) {
gl_Position = vec4(coordinates, 1.0);
uv = coordinates.xy * 0.5 + 0.5;
}`;
var vert_shader = gl.createShader(gl.VERTEX_SHADER);
{
gl.shaderSource(vert_shader, shader_std + vert_code);
gl.compileShader(vert_shader);
var compilation_status = gl.getShaderParameter(vert_shader, gl.COMPILE_STATUS);
if (!compilation_status)
{
var compilation_log = gl.getShaderInfoLog(vert_shader);
console.error('Fragment shader compiler log: ' + compilation_log);
return null;
}
}
var frag_shader = gl.createShader(gl.FRAGMENT_SHADER);
{
gl.shaderSource(frag_shader, shader_std + frag_code);
gl.compileShader(frag_shader);
var compilation_status = gl.getShaderParameter(frag_shader, gl.COMPILE_STATUS);
if (!compilation_status)
{
var compilation_log = gl.getShaderInfoLog(frag_shader);
console.log(frag_code);
console.error('Fragment shader compiler log: ' + compilation_log);
document.getElementById('texture_visualization_code_log').value = 'Compilation failed:\n' + compilation_log;
return null;
}
}
var shader_program = gl.createProgram();
{
gl.attachShader(shader_program, vert_shader);
gl.attachShader(shader_program, 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);
document.getElementById('texture_visualization_code_log').value = 'Link failed:\n' + link_log;
}
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;
shader_program = this.compile_screen_shader_program(this.subresource_desc['webgl_pixel_shader_private'] + 'void main(void) { display = vec4(0.0, 0.0, 0.0, 0.0);\n' + user_frag_code + `\n}`);
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_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));
}
// -------------------------------------------------------------------- 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);
}
// -------------------------------------------------------------------- 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`);
}
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 w = window.innerWidth;
var h = window.innerHeight;
var top_h = 40;
document.getElementById('onresize_body_top_bar').style.width = `${w}px`;
document.getElementById('onresize_body_top_bar').style.height = `${top_h}px`;
document.getElementById('onresize_body_left').style.width = `${Math.floor(w * 0.25)}px`;
document.getElementById('onresize_body_left').style.height = `${h - top_h}px`;
document.getElementById('onresize_body_right').style.width = `${w - Math.floor(w * 0.25)}px`;
document.getElementById('onresize_body_right').style.height = `${h - top_h}px`;
document.getElementById('onresize_body_right').style.width = `${w - Math.floor(w * 0.25)}px`;
document.getElementById('onresize_body_right').style.height = `${h - top_h}px`;
{
var search_h = (30 + 2 * 5) * 2;
document.getElementById('pass_hierarchy').style.width = `${Math.floor(w * 0.25) - 10 * 2}px`;
document.getElementById('pass_hierarchy').style.height = `${h - top_h - search_h - 10 * 2}px`;
}
{
//document.getElementById('main_right_pannel').style.width = document.getElementById('onresize_body_right').style.width;
document.getElementById('main_right_pannel').style.height = `${h - top_h - 10 * 2}px`;
}
if (g_view !== null)
{
var ctx = {};
ctx.width = w - Math.floor(w * 0.25);
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;
}
/* ----------------------------------------------------- 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);
cursor: pointer;
border-radius: 0;
}
.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: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: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:active,
.button_list a.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.error,
.button_list a.error:active
{
color: var(--error-txt-color);
}
/* ----------------------------------------------------- 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
{
width: auto;
overflow-x: auto;
overflow-y: scroll;
}
#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>