matdbg: UI refresh (#7301)
This commit is contained in:
@@ -41,8 +41,8 @@ set(SRCS
|
||||
set(RESOURCE_DIR ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(RESOURCE_BINS
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/web/style.css
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/web/script.js
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/web/api.js
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/web/app.js
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/web/index.html
|
||||
)
|
||||
|
||||
|
||||
@@ -50,13 +50,28 @@ using utils::FixedCapacityVector;
|
||||
// serves files directly from the source code tree.
|
||||
#define SERVE_FROM_SOURCE_TREE 0
|
||||
|
||||
// When set to 1, we will serve an experimental frontend, which will potentially replace the current
|
||||
// frontend when ready.
|
||||
#define EXPERIMENTAL_WEB_FRAMEWORK 0
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
|
||||
namespace {
|
||||
std::string const BASE_URL = "libs/matdbg/web";
|
||||
} // anonymous
|
||||
|
||||
#else
|
||||
|
||||
#if !SERVE_FROM_SOURCE_TREE
|
||||
#include "matdbg_resources.h"
|
||||
#endif
|
||||
#include <unordered_map>
|
||||
|
||||
namespace {
|
||||
|
||||
struct Asset {
|
||||
std::string_view mime;
|
||||
std::string_view data;
|
||||
};
|
||||
std::unordered_map<std::string_view, Asset> ASSET_MAP;
|
||||
|
||||
} // anonymous
|
||||
|
||||
#endif // SERVE_FROM_SOURCE_TREE
|
||||
|
||||
namespace filament::matdbg {
|
||||
|
||||
@@ -74,14 +89,6 @@ std::string_view const DebugServer::kErrorHeader =
|
||||
"HTTP/1.1 404 Not Found\r\nContent-Type: %s\r\n"
|
||||
"Connection: close\r\n\r\n";
|
||||
|
||||
#if EXPERIMENTAL_WEB_FRAMEWORK
|
||||
|
||||
namespace {
|
||||
|
||||
std::string const BASE_URL = "libs/matdbg/web/experiment";
|
||||
|
||||
} // anonymous
|
||||
|
||||
class FileRequestHandler : public CivetHandler {
|
||||
public:
|
||||
FileRequestHandler(DebugServer* server) : mServer(server) {}
|
||||
@@ -92,66 +99,44 @@ public:
|
||||
if (uri == "/") {
|
||||
uri = "/index.html";
|
||||
}
|
||||
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
if (uri == "/index.html" || uri == "/app.js" || uri == "/api.js") {
|
||||
mg_send_file(conn, (BASE_URL + uri).c_str());
|
||||
return true;
|
||||
}
|
||||
slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl;
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
DebugServer* mServer;
|
||||
};
|
||||
|
||||
#else
|
||||
class FileRequestHandler : public CivetHandler {
|
||||
public:
|
||||
FileRequestHandler(DebugServer* server) : mServer(server) {}
|
||||
bool handleGet(CivetServer *server, struct mg_connection *conn) {
|
||||
auto const& kSuccessHeader = DebugServer::kSuccessHeader;
|
||||
|
||||
const struct mg_request_info* request = mg_get_request_info(conn);
|
||||
std::string uri(request->request_uri);
|
||||
if (uri == "/" || uri == "/index.html") {
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
mg_send_file(conn, "libs/matdbg/web/index.html");
|
||||
#else
|
||||
mg_printf(conn, kSuccessHeader.data(), "text/html");
|
||||
mg_write(conn, mServer->mHtml.c_str(), mServer->mHtml.size());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
if (uri == "/style.css") {
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
mg_send_file(conn, "libs/matdbg/web/style.css");
|
||||
#else
|
||||
mg_printf(conn, kSuccessHeader.data(), "text/css");
|
||||
mg_write(conn, mServer->mCss.c_str(), mServer->mCss.size());
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
if (uri == "/script.js") {
|
||||
#if SERVE_FROM_SOURCE_TREE
|
||||
mg_send_file(conn, "libs/matdbg/web/script.js");
|
||||
#else
|
||||
mg_printf(conn, kSuccessHeader.data(), "text/javascript");
|
||||
mg_write(conn, mServer->mJavascript.c_str(), mServer->mJavascript.size());
|
||||
#endif
|
||||
auto const& asset_itr = ASSET_MAP.find(uri);
|
||||
if (asset_itr != ASSET_MAP.end()) {
|
||||
auto const& mime = asset_itr->second.mime;
|
||||
auto const& data = asset_itr->second.data;
|
||||
mg_printf(conn, kSuccessHeader.data(), mime.data());
|
||||
mg_write(conn, data.data(), data.size());
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
slog.e << "DebugServer: bad request at line " << __LINE__ << ": " << uri << io::endl;
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
DebugServer* mServer;
|
||||
};
|
||||
#endif
|
||||
|
||||
DebugServer::DebugServer(Backend backend, int port) : mBackend(backend) {
|
||||
|
||||
#if !SERVE_FROM_SOURCE_TREE
|
||||
mHtml = CString((const char*) MATDBG_RESOURCES_INDEX_DATA, MATDBG_RESOURCES_INDEX_SIZE - 1);
|
||||
mJavascript = CString((const char*) MATDBG_RESOURCES_SCRIPT_DATA, MATDBG_RESOURCES_SCRIPT_SIZE - 1);
|
||||
mCss = CString((const char*) MATDBG_RESOURCES_STYLE_DATA, MATDBG_RESOURCES_STYLE_SIZE - 1);
|
||||
ASSET_MAP["/index.html"] = {
|
||||
.mime = "text/html",
|
||||
.data = {(char const*) MATDBG_RESOURCES_INDEX_DATA},
|
||||
};
|
||||
ASSET_MAP["/app.js"] = {
|
||||
.mime = "text/javascript",
|
||||
.data = {(char const*) MATDBG_RESOURCES_APP_DATA},
|
||||
};
|
||||
ASSET_MAP["/api.js"] = {
|
||||
.mime = "text/javascript",
|
||||
.data = {(char const*) MATDBG_RESOURCES_API_DATA},
|
||||
};
|
||||
#endif
|
||||
|
||||
// By default the server spawns 50 threads so we override this to 10. According to the civetweb
|
||||
|
||||
@@ -242,6 +242,10 @@ class CodeViewer extends LitElement {
|
||||
}
|
||||
|
||||
_rebuild() {
|
||||
if (!this.active || !this.modified) {
|
||||
console.log('Called rebuild while variant is inactive or unmodified');
|
||||
return;
|
||||
}
|
||||
this.dispatchEvent(new CustomEvent(
|
||||
'rebuild-shader',
|
||||
{detail: this.editor.getValue(), bubbles: true, composed: true}
|
||||
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Filament Debugger</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: "Open Sans";
|
||||
}
|
||||
</style>
|
||||
<script src="api.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/loader.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js" type="module"></script>
|
||||
<matdbg-viewer></matdbg-viewer>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,96 +1,23 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<head>
|
||||
<title>Filament Debugger</title>
|
||||
<meta charset="utf-8">
|
||||
<link rel="icon" type="image/png" href="https://google.github.io/filament/favicon.png" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Lexend Deca" rel="stylesheet">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body class="vbox viewport">
|
||||
|
||||
<header>matdbg</header>
|
||||
|
||||
<section class="main hbox space-between">
|
||||
<nav class="vbox">
|
||||
<div id="material-list" class="scrollable squishy">
|
||||
</div>
|
||||
<div id="material-detail" class="scrollable stretchy">
|
||||
</div>
|
||||
</nav>
|
||||
<article id="shader-source">
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<footer> </footer>
|
||||
|
||||
<template id="material-list-template">
|
||||
<div style="white-space: nowrap">
|
||||
{{#item}}
|
||||
{{#is_label}}
|
||||
<br/><b>{{label}}</b>
|
||||
{{/is_label}}
|
||||
{{#is_material}}
|
||||
<a class="material {{classes}}" data-matid="{{matid}}">{{name}}</a>
|
||||
{{/is_material}}
|
||||
<br/>
|
||||
{{/item}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="material-detail-template">
|
||||
<div style="white-space: pre">
|
||||
name = {{ name }}
|
||||
version = {{ version }}
|
||||
{{#required_attributes.length}}
|
||||
|
||||
<b>Required attributes</b>
|
||||
{{#required_attributes}}
|
||||
{{.}}
|
||||
{{/required_attributes}}
|
||||
{{/required_attributes.length}}
|
||||
|
||||
<b>OpenGL shaders</b>
|
||||
{{#opengl}}
|
||||
<a class="shader {{classes}}" data-glindex="{{index}}">{{index}} {{shaderModel}} {{pipelineStage}} {{variantString}}</a>
|
||||
{{/opengl}}
|
||||
|
||||
<b>Vulkan shaders</b>
|
||||
{{#vulkan}}
|
||||
<a class="shader {{classes}}" data-vkindex="{{index}}">{{index}} {{shaderModel}} {{pipelineStage}} {{variantString}}</a>
|
||||
{{/vulkan}}
|
||||
|
||||
<b>Metal shaders</b>
|
||||
{{#metal}}
|
||||
<a class="shader {{classes}}" data-metalindex="{{index}}">{{index}} {{shaderModel}} {{pipelineStage}} {{variantString}}</a>
|
||||
{{/metal}}
|
||||
|
||||
<b>Material details</b>
|
||||
{{#shading}}
|
||||
shading model = {{model}}
|
||||
vertex domain = {{vertex_domain}}
|
||||
interpolation = {{interpolation}}
|
||||
shadow multiply = {{shadow_multiply}}
|
||||
specular antialiasing = {{specular_antialiasing}}
|
||||
variance = {{variance}}
|
||||
threshold = {{threshold}}
|
||||
clear coat IOR change = {{clear_coat_IOR_change}}
|
||||
{{/shading}}
|
||||
{{#raster}}
|
||||
blending = {{blending}}
|
||||
mask threshold = {{mask_threshold}}
|
||||
color write = {{color_write}}
|
||||
depth write = {{depth_write}}
|
||||
depth test = {{depth_test}}
|
||||
double sided = {{double_sided}}
|
||||
culling = {{culling}}
|
||||
transparency = {{transparency}}
|
||||
{{/raster}}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mustache.js/3.0.1/mustache.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.17.1/min/vs/loader.js"></script>
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
<meta name="viewport" content="width=device-width,user-scalable=no,initial-scale=1">
|
||||
<link href="https://google.github.io/filament/favicon.png" rel="icon" type="image/x-icon" />
|
||||
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,600,700" rel="stylesheet">
|
||||
<style>
|
||||
html, body {
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
font-family: "Open Sans";
|
||||
}
|
||||
</style>
|
||||
<script src="api.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/vs/loader.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script src="app.js" type="module"></script>
|
||||
<matdbg-viewer></matdbg-viewer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,516 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
|
||||
*/
|
||||
const kMonacoBaseUrl = 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.25.2/min/';
|
||||
const kUntitledPlaceholder = "untitled";
|
||||
|
||||
const materialList = document.getElementById("material-list");
|
||||
const materialDetail = document.getElementById("material-detail");
|
||||
const header = document.querySelector("header");
|
||||
const footer = document.querySelector("footer");
|
||||
const shaderSource = document.getElementById("shader-source");
|
||||
const matDetailTemplate = document.getElementById("material-detail-template");
|
||||
const matListTemplate = document.getElementById("material-list-template");
|
||||
|
||||
const STATUS_LOOP_TIMEOUT = 3000;
|
||||
|
||||
const gMaterialDatabase = {};
|
||||
|
||||
let gEditor = null;
|
||||
let gCurrentMaterial = "00000000";
|
||||
let gCurrentLanguage = "glsl";
|
||||
let gCurrentShader = { matid: "00000000", glindex: 0 };
|
||||
let gEditorIsLoading = false;
|
||||
|
||||
require.config({ paths: { "vs": `${kMonacoBaseUrl}vs` }});
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorkerUrl: function() {
|
||||
return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
|
||||
self.MonacoEnvironment = {
|
||||
baseUrl: '${kMonacoBaseUrl}'
|
||||
};
|
||||
importScripts('${kMonacoBaseUrl}vs/base/worker/workerMain.js');`
|
||||
)}`;
|
||||
}
|
||||
};
|
||||
|
||||
function getShaderAPI(selection) {
|
||||
if (!selection) {
|
||||
selection = gCurrentShader;
|
||||
}
|
||||
if ("glindex" in selection) return "opengl";
|
||||
if ("vkindex" in selection) return "vulkan";
|
||||
if ("metalindex" in selection) return "metal";
|
||||
return "error";
|
||||
}
|
||||
|
||||
function rebuildMaterial() {
|
||||
let api = 0, index = -1;
|
||||
|
||||
const shader = getShaderRecord(gCurrentShader);
|
||||
const shaderApi = getShaderAPI();
|
||||
|
||||
switch (shaderApi) {
|
||||
case "opengl": api = 1; index = gCurrentShader.glindex; break;
|
||||
case "vulkan": api = 2; index = gCurrentShader.vkindex; break;
|
||||
case "metal": api = 3; index = gCurrentShader.metalindex; break;
|
||||
}
|
||||
|
||||
if (shaderApi === "vulkan") {
|
||||
if (gCurrentLanguage === "glsl") {
|
||||
delete shader["spirv"];
|
||||
} else if (gCurrentLanguage === "spirv") {
|
||||
delete shader["glsl"];
|
||||
}
|
||||
}
|
||||
|
||||
const editedText = shader[gCurrentLanguage];
|
||||
const req = new XMLHttpRequest();
|
||||
req.open('POST', '/api/edit');
|
||||
req.send(`${gCurrentShader.matid} ${api} ${index} ${editedText}`);
|
||||
}
|
||||
|
||||
document.querySelector("body").addEventListener("click", (evt) => {
|
||||
const anchor = evt.target.closest("a");
|
||||
if (!anchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle selection of a material.
|
||||
if (anchor.classList.contains("material")) {
|
||||
selectMaterial(anchor.dataset.matid, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle selection of a shader.
|
||||
if (anchor.classList.contains("shader")) {
|
||||
selectShader(anchor.dataset);
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle a rebuild.
|
||||
if (anchor.classList.contains("rebuild")) {
|
||||
rebuildMaterial();
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle language selection.
|
||||
for (const lang of "glsl spirv msl".split(" ")) {
|
||||
if (anchor.classList.contains(lang)) {
|
||||
gCurrentLanguage = lang;
|
||||
selectShader(gCurrentShader);
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle Ctrl+Arrow for fast keyboard navigation between shader variants and materials. Either the
|
||||
// materialStep or shaderStep argument can be non-zero (not both) and they must be -1, 0, or +1.
|
||||
// TODO: this function could be vastly simplified by changing the format of the shader selector.
|
||||
function selectNextShader(materialStep, shaderStep) {
|
||||
if (materialStep !== 0) {
|
||||
const matids = getDisplayedMaterials().map(m => m.matid).filter(m => m);
|
||||
const currentIndex = matids.indexOf(gCurrentMaterial);
|
||||
const nextIndex = currentIndex + materialStep;
|
||||
if (nextIndex >= 0 && nextIndex < matids.length) {
|
||||
selectMaterial(matids[nextIndex], true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const material = gMaterialDatabase[gCurrentMaterial];
|
||||
const variants = [];
|
||||
let currentIndex = 0;
|
||||
for (const [index, shader] of material.opengl.entries()) {
|
||||
if (index === gCurrentShader.glindex) currentIndex = variants.length;
|
||||
variants.push({ matid, glindex: index });
|
||||
}
|
||||
for (const [index, shader] of material.vulkan.entries()) {
|
||||
if (index === gCurrentShader.vkindex) currentIndex = variants.length;
|
||||
variants.push({ matid, vkindex: index });
|
||||
}
|
||||
for (const [index, shader] of material.metal.entries()) {
|
||||
if (index === gCurrentShader.metalindex) currentIndex = variants.length;
|
||||
variants.push({ matid, metalindex: index });
|
||||
}
|
||||
const nextIndex = currentIndex + shaderStep;
|
||||
if (nextIndex >= 0 && nextIndex < variants.length) {
|
||||
selectShader(variants[nextIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchMaterial(matid) {
|
||||
fetch(`api/material?matid=${matid}`).then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(matInfo) {
|
||||
if (matid in gMaterialDatabase) {
|
||||
return;
|
||||
}
|
||||
matInfo.matid = matid;
|
||||
gMaterialDatabase[matid] = matInfo;
|
||||
renderMaterialList();
|
||||
});
|
||||
}
|
||||
|
||||
function queryActiveShaders() {
|
||||
if (!isConnected()) {
|
||||
return;
|
||||
}
|
||||
fetch("api/active").then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(activeMaterials) {
|
||||
// The only active materials are the ones with active variants.
|
||||
for (matid in gMaterialDatabase) {
|
||||
const material = gMaterialDatabase[matid];
|
||||
material.active = false;
|
||||
}
|
||||
for (matid in activeMaterials) {
|
||||
const material = gMaterialDatabase[matid];
|
||||
const activeBackend = activeMaterials[matid][0];
|
||||
const activeShaders = activeMaterials[matid].slice(1);
|
||||
for (const shader of material[activeBackend]) {
|
||||
shader.active = activeShaders.indexOf(shader.variant) > -1;
|
||||
material.active = material.active || shader.active;
|
||||
}
|
||||
}
|
||||
renderMaterialList();
|
||||
renderMaterialDetail();
|
||||
})
|
||||
.catch(error => {
|
||||
// This can occur if the JSON is invalid.
|
||||
console.error(error);
|
||||
});
|
||||
}
|
||||
|
||||
function isConnected() {
|
||||
return footer.innerText == 'connected';
|
||||
}
|
||||
|
||||
function onConnected() {
|
||||
footer.innerText = 'connected';
|
||||
fetch("api/matids").then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(matInfo) {
|
||||
for (matid of matInfo) {
|
||||
if (!(matid in gMaterialDatabase)) {
|
||||
fetchMaterial(matid);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onDisconnected() {
|
||||
footer.innerText = 'not connected';
|
||||
for (matid in gMaterialDatabase) {
|
||||
const material = gMaterialDatabase[matid];
|
||||
material.active = false;
|
||||
for (const shader of material.opengl) shader.active = false;
|
||||
for (const shader of material.vulkan) shader.active = false;
|
||||
for (const shader of material.metal) shader.active = false;
|
||||
}
|
||||
renderMaterialList();
|
||||
renderMaterialDetail();
|
||||
}
|
||||
|
||||
function statusLoop() {
|
||||
// This is a hanging get except for when transition from disconnected to connected, which
|
||||
// should return immediately.
|
||||
fetch("api/status" + (isConnected() ? '' : '?firstTime'))
|
||||
.then(async (response) => {
|
||||
const matid = await response.text();
|
||||
// A first-time request returned successfully
|
||||
if (matid === '0') {
|
||||
onConnected();
|
||||
} else if (matid != '1') {
|
||||
fetchMaterial(matid);
|
||||
} // else matid == '1' and it's a no-op, we just loop again.
|
||||
statusLoop();
|
||||
})
|
||||
.catch(err => {
|
||||
onDisconnected();
|
||||
setTimeout(statusLoop, STATUS_LOOP_TIMEOUT)
|
||||
});
|
||||
}
|
||||
|
||||
function fetchMaterials() {
|
||||
fetch("api/materials").then(function(response) {
|
||||
return response.json();
|
||||
}).then(function(matJson) {
|
||||
for (const matInfo of matJson) {
|
||||
if (matInfo.matid in gMaterialDatabase) {
|
||||
continue;
|
||||
}
|
||||
gMaterialDatabase[matInfo.matid] = matInfo;
|
||||
}
|
||||
selectMaterial(matJson[0].matid, true);
|
||||
});
|
||||
}
|
||||
|
||||
function fetchShader(selection, matinfo, onDone) {
|
||||
let query, target, index;
|
||||
switch (getShaderAPI(selection)) {
|
||||
case "opengl":
|
||||
index = parseInt(selection.glindex);
|
||||
query = `type=${gCurrentLanguage}&glindex=${index}`;
|
||||
target = matinfo.opengl[index];
|
||||
break;
|
||||
case "vulkan":
|
||||
index = parseInt(selection.vkindex);
|
||||
query = `type=${gCurrentLanguage}&vkindex=${index}`;
|
||||
target = matinfo.vulkan[index];
|
||||
break;
|
||||
case "metal":
|
||||
index = parseInt(selection.metalindex);
|
||||
query = `type=${gCurrentLanguage}&metalindex=${index}`;
|
||||
target = matinfo.metal[index];
|
||||
break;
|
||||
}
|
||||
fetch(`api/shader?matid=${matinfo.matid}&${query}`).then(function(response) {
|
||||
return response.text();
|
||||
}).then(function(shaderText) {
|
||||
target[gCurrentLanguage] = shaderText;
|
||||
onDone();
|
||||
});
|
||||
}
|
||||
|
||||
function getDisplayedMaterials() {
|
||||
const items = [];
|
||||
|
||||
// Names need not be unique, so we display a numeric suffix for non-unique names.
|
||||
// To achieve stable ordering of anonymous materials, we first sort by matid.
|
||||
const labels = new Set();
|
||||
const matids = Object.keys(gMaterialDatabase).sort();
|
||||
const duplicatedLabels = {};
|
||||
for (const matid of matids) {
|
||||
const name = gMaterialDatabase[matid].name || kUntitledPlaceholder;
|
||||
if (labels.has(name)) {
|
||||
duplicatedLabels[name] = 0;
|
||||
} else {
|
||||
labels.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
// Build a list of objects to pass into the template string.
|
||||
for (const matid of matids) {
|
||||
const item = Object.assign({}, gMaterialDatabase[matid]);
|
||||
item.classes = matid === gCurrentMaterial ? "current " : "";
|
||||
if (!item.active) {
|
||||
item.classes += "inactive "
|
||||
}
|
||||
item.domain = item.shading.material_domain === "surface" ? "surface" : "postpro";
|
||||
item.is_material = true;
|
||||
|
||||
const name = item.name || kUntitledPlaceholder;
|
||||
if (name in duplicatedLabels) {
|
||||
const index = duplicatedLabels[name];
|
||||
item.name = `${name} (${index})`;
|
||||
duplicatedLabels[name] = index + 1;
|
||||
} else {
|
||||
item.name = name;
|
||||
}
|
||||
|
||||
items.push(item);
|
||||
}
|
||||
|
||||
// The template takes a flat list of items, so here we insert items for section headers using
|
||||
// blank names, which causes them to sort to the top of their respective sections.
|
||||
const sectionLabel = {"is_label": true, "name": ""};
|
||||
items.push(Object.assign({"label": "Surface materials", "domain": "surface"}, sectionLabel));
|
||||
items.push(Object.assign({"label": "PostProcess materials", "domain": "postpro"}, sectionLabel));
|
||||
|
||||
// Next, sort all materials and section headers.
|
||||
items.sort((a, b) => {
|
||||
if (a.domain > b.domain) return -1;
|
||||
if (a.domain < b.domain) return +1;
|
||||
if (a.name < b.name) return -1;
|
||||
if (a.name > b.name) return +1;
|
||||
return 0;
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
function renderMaterialList() {
|
||||
const items = getDisplayedMaterials();
|
||||
materialList.innerHTML = Mustache.render(matListTemplate.innerHTML, { "item": items } );
|
||||
}
|
||||
|
||||
function updateClassList(array, indexProperty, selectedIndex) {
|
||||
for (let item of array) {
|
||||
const current = parseInt(item[indexProperty]) === selectedIndex;
|
||||
item.classes = current ? "current " : "";
|
||||
if (!item.active) {
|
||||
item.classes += "inactive "
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function renderMaterialDetail() {
|
||||
const mat = gMaterialDatabase[gCurrentMaterial];
|
||||
const ok = mat.matid === gCurrentShader.matid;
|
||||
updateClassList(mat.opengl, "index", ok ? parseInt(gCurrentShader.glindex) : -1);
|
||||
updateClassList(mat.vulkan, "index", ok ? parseInt(gCurrentShader.vkindex) : -1);
|
||||
updateClassList(mat.metal, "index", ok ? parseInt(gCurrentShader.metalindex) : -1);
|
||||
const item = Object.assign({}, mat);
|
||||
if (item.shading.material_domain !== "surface") {
|
||||
delete item.shading;
|
||||
}
|
||||
materialDetail.innerHTML = Mustache.render(matDetailTemplate.innerHTML, item);
|
||||
}
|
||||
|
||||
function getShaderRecord(selection) {
|
||||
const mat = gMaterialDatabase[gCurrentMaterial];
|
||||
if (selection.glindex >= 0) return mat.opengl[parseInt(selection.glindex)];
|
||||
if (selection.vkindex >= 0) return mat.vulkan[parseInt(selection.vkindex)];
|
||||
if (selection.metalindex >= 0) return mat.metal[parseInt(selection.metalindex)];
|
||||
return null;
|
||||
}
|
||||
|
||||
function renderShaderStatus() {
|
||||
const shader = getShaderRecord(gCurrentShader);
|
||||
let statusString = "";
|
||||
if (shader) {
|
||||
const glsl = "glsl " + (gCurrentLanguage === "glsl" ? "active" : "");
|
||||
const msl = "msl " + (gCurrentLanguage === "msl" ? "active" : "");
|
||||
const spirv = "spirv " + (gCurrentLanguage === "spirv" ? "active" : "");
|
||||
switch (getShaderAPI()) {
|
||||
case "opengl":
|
||||
statusString += ` <a class='status_button ${glsl}'>[GLSL]</a>`;
|
||||
break;
|
||||
case "metal":
|
||||
statusString += ` <a class='status_button ${msl}'>[MSL]</a>`;
|
||||
break;
|
||||
case "vulkan":
|
||||
statusString += ` <a class='status_button ${glsl}'>[GLSL]</a>`;
|
||||
statusString += ` <a class='status_button ${spirv}'>[SPIRV]</a>`;
|
||||
break;
|
||||
}
|
||||
if (shader.modified && gCurrentLanguage !== "spirv") {
|
||||
statusString += " <a class='status_button rebuild'>[rebuild]</a>";
|
||||
}
|
||||
if (!shader.active) {
|
||||
statusString += " <span class='warning'> selected variant is inactive </span>";
|
||||
}
|
||||
}
|
||||
header.innerHTML = "matdbg" + statusString;
|
||||
}
|
||||
|
||||
function selectShader(selection) {
|
||||
const shader = getShaderRecord(selection);
|
||||
if (!shader) {
|
||||
console.error("Shader not yet available.")
|
||||
return;
|
||||
}
|
||||
|
||||
// Change the current language selection if necessary.
|
||||
switch (getShaderAPI(selection)) {
|
||||
case "opengl":
|
||||
if (gCurrentLanguage !== "glsl") {
|
||||
gCurrentLanguage = "glsl";
|
||||
}
|
||||
break;
|
||||
case "vulkan":
|
||||
if (gCurrentLanguage !== "spirv" && gCurrentLanguage !== "glsl") {
|
||||
gCurrentLanguage = "spirv";
|
||||
}
|
||||
break;
|
||||
case "metal":
|
||||
if (gCurrentLanguage !== "msl") {
|
||||
gCurrentLanguage = "msl";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const showShaderSource = () => {
|
||||
gCurrentShader = selection;
|
||||
gCurrentShader.matid = gCurrentMaterial;
|
||||
renderMaterialDetail();
|
||||
gEditorIsLoading = true;
|
||||
gEditor.setValue(shader[gCurrentLanguage]);
|
||||
gEditorIsLoading = false;
|
||||
shaderSource.style.visibility = "visible";
|
||||
renderShaderStatus();
|
||||
};
|
||||
if (!shader[gCurrentLanguage]) {
|
||||
const matInfo = gMaterialDatabase[gCurrentMaterial];
|
||||
fetchShader(selection, matInfo, showShaderSource);
|
||||
} else {
|
||||
showShaderSource();
|
||||
}
|
||||
}
|
||||
|
||||
function onEdit(changes) {
|
||||
if (gEditorIsLoading) {
|
||||
return;
|
||||
}
|
||||
const shader = getShaderRecord(gCurrentShader);
|
||||
if (!shader) {
|
||||
return;
|
||||
}
|
||||
if (!shader.modified) {
|
||||
shader.modified = true;
|
||||
renderShaderStatus();
|
||||
}
|
||||
shader[gCurrentLanguage] = gEditor.getValue();
|
||||
}
|
||||
|
||||
function selectMaterial(matid, selectFirstShader) {
|
||||
gCurrentMaterial = matid;
|
||||
renderMaterialList();
|
||||
renderMaterialDetail();
|
||||
if (selectFirstShader) {
|
||||
const mat = gMaterialDatabase[gCurrentMaterial];
|
||||
const selection = { matid };
|
||||
if (mat.opengl.length > 0) selection.glindex = 0;
|
||||
else if (mat.vulkan.length > 0) selection.vkindex = 0;
|
||||
else if (mat.metal.length > 0) selection.metalindex = 0;
|
||||
selectShader(selection);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
require(["vs/editor/editor.main"], function () {
|
||||
const KeyMod = monaco.KeyMod, KeyCode = monaco.KeyCode;
|
||||
gEditor = monaco.editor.create(shaderSource, {
|
||||
value: "",
|
||||
language: "cpp",
|
||||
scrollBeyondLastLine: false,
|
||||
readOnly: false,
|
||||
minimap: { enabled: false }
|
||||
});
|
||||
gEditor.onDidChangeModelContent((e) => { onEdit(e.changes); });
|
||||
|
||||
gEditor.addCommand(KeyMod.CtrlCmd | KeyCode.KEY_S, () => rebuildMaterial());
|
||||
|
||||
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.UpArrow, () => selectNextShader(-1, 0));
|
||||
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.DownArrow, () => selectNextShader(+1, 0));
|
||||
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.LeftArrow, () => selectNextShader(0, -1));
|
||||
gEditor.addCommand(KeyMod.Shift | KeyMod.WinCtrl | KeyCode.RightArrow, () => selectNextShader(0, +1));
|
||||
|
||||
fetchMaterials();
|
||||
});
|
||||
|
||||
Mustache.parse(matDetailTemplate.innerHTML);
|
||||
Mustache.parse(matListTemplate.innerHTML);
|
||||
|
||||
statusLoop();
|
||||
|
||||
// Poll for active shaders once every second.
|
||||
// Take care not to poll more frequently than the frame rate. Active variants are determined
|
||||
// by the list of variants that were fetched between this query and the previous query.
|
||||
setInterval(queryActiveShaders, 1000);
|
||||
}
|
||||
|
||||
init();
|
||||
@@ -1,110 +0,0 @@
|
||||
html, body, .viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Lexend Deca', sans-serif;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a, a:visited {
|
||||
text-decoration: none;
|
||||
color: #567;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a:hover, a.current {
|
||||
font-weight: bold;
|
||||
color: #07f;
|
||||
}
|
||||
|
||||
a.status_button {
|
||||
color: #e4e682;
|
||||
}
|
||||
|
||||
a.status_button.active {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
span.warning {
|
||||
color: black;
|
||||
background: orange;
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
a.inactive {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
a.inactive.current {
|
||||
color: #5af;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: Menlo, Monaco, "Courier New", monospace;
|
||||
}
|
||||
|
||||
.vbox {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
position: relative;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.scrollable > * {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.squishy {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.stretchy {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.hbox {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.space-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
header, footer {
|
||||
height: 26px;
|
||||
background: cornflowerblue;
|
||||
padding: 5px 0 0 5px;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
article {
|
||||
flex: 5;
|
||||
border-top: solid 2px;
|
||||
border-bottom: solid 2px;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
nav {
|
||||
border: solid 2px;
|
||||
font-size: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
nav > *:first-child {
|
||||
border-bottom: solid 2px;
|
||||
}
|
||||
|
||||
nav > * {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
Reference in New Issue
Block a user