();
btnList.push({
name: 'Open in new window ↗',
onClick: function(_event) {
window.open(plugin.fullUrl, '_blank');
consoleInstance?.hide();
}
});
btnList.push({
name: 'Reload',
onClick: function(_event) {
const iframe = getIframe();
if (iframe) iframe.src = plugin.fullUrl;
}
});
btnList.push({
name: 'Fullscreen',
onClick: function(_event) {
const iframe = getIframe();
if (iframe.requestFullscreen) {
iframe.requestFullscreen();
} else if (iframe["webkitRequestFullscreen"] instanceof Function) {
iframe["webkitRequestFullscreen"]();
}
}
});
callback(btnList);
});
return plugin;
}
const CONTAINER_STYLE = "padding: 10px; font-family: monospace;";
const HEADING_STYLE = "margin-bottom: 10px;";
const SUBHEADING_STYLE = "margin-bottom: 10px; margin-top: 15px;";
const TABLE_STYLE = "width: 100%; border-collapse: collapse; border: 1px solid rgba(0,0,0,0.1); table-layout: fixed;";
const CELL_STYLE = "border: 1px solid rgba(0,0,0,0.1); padding: 5px;";
const HEADER_CELL_STYLE = CELL_STYLE;
const VALUE_CELL_STYLE = CELL_STYLE + " word-break: break-all;";
function createTable(rows: Array<{label: string, value: any}>, sortByValue: boolean = false): string {
if (sortByValue) {
rows.sort((a, b) => (b.value ? 1 : 0) - (a.value ? 1 : 0));
}
let html = ``;
html += "";
for (const row of rows) {
const value = typeof row.value === 'boolean' ? (row.value ? "✅" : "❌") : row.value;
html += `| ${row.label} | ${value} |
`;
}
html += "
";
return html;
}
function getWebGL2Support(): string {
try {
if (probeGl2()) {
return "✅";
}
} catch (e) {
// WebGL2 not supported
}
return "❌";
}
function createDeviceUtilitiesPlugin() {
if (!globalThis.VConsole) return;
const plugin = new VConsole.VConsolePlugin("device-utilities", "📱 Device Info");
plugin.on('renderTab', function(callback) {
let html = ``;
// Device type
const deviceType = getDeviceType();
html += `
Device: ${deviceType}
`;
// Device capabilities table
html += createTable([
{ label: "💻 Desktop", value: DeviceUtilities.isDesktop() },
{ label: "📱 Mobile Device", value: DeviceUtilities.isMobileDevice() },
{ label: "🍎 iOS", value: DeviceUtilities.isiOS() },
{ label: "📱 iPad", value: DeviceUtilities.isiPad() },
{ label: "🤖 Android", value: DeviceUtilities.isAndroidDevice() },
{ label: "🦊 Mozilla XR", value: DeviceUtilities.isMozillaXR() },
{ label: "🌵 Needle App Clip", value: DeviceUtilities.isNeedleAppClip() },
{ label: "🍏 macOS", value: DeviceUtilities.isMacOS() },
{ label: "👓 VisionOS", value: DeviceUtilities.isVisionOS() },
{ label: "🧭 Safari", value: DeviceUtilities.isSafari() },
{ label: "🕶️ Meta Quest", value: DeviceUtilities.isQuest() },
{ label: "🔗 QuickLook AR Support", value: DeviceUtilities.supportsQuickLookAR() },
], true);
// Versions
const versionRows: Array<{label: string, value: any}> = [];
const iosVersion = DeviceUtilities.getiOSVersion();
if (iosVersion) versionRows.push({ label: "🍎 iOS Version", value: iosVersion });
const chromeVersion = DeviceUtilities.getChromeVersion();
if (chromeVersion) versionRows.push({ label: "🌐 Chrome Version", value: chromeVersion });
const safariVersion = DeviceUtilities.getSafariVersion();
if (safariVersion) versionRows.push({ label: "🧭 Safari Version", value: safariVersion });
if (versionRows.length > 0) {
html += createTable(versionRows, false);
}
html += "";
// User Agent table
html += ``;
html += `
User Agent Info
`;
const userAgentRows = [
{ label: "User Agent", value: navigator.userAgent },
{ label: "Platform", value: navigator.platform },
{ label: "App Version", value: navigator.appVersion },
// @ts-ignore
{ label: "User Agent Data", value: navigator.userAgentData ? `Platform: ${navigator.userAgentData.platform}, Mobile: ${navigator.userAgentData.mobile}` : "Not supported" },
{ label: "WebXR", value: 'xr' in navigator ? "✅" : "❌" },
{ label: "WebGPU", value: 'gpu' in navigator ? "✅" : "❌" },
{ label: "WebGL 2", value: getWebGL2Support() },
];
html += createTable(userAgentRows, false);
html += "";
callback(html);
});
return plugin;
}
function createGraphicsInfoPlugin() {
if (!globalThis.VConsole) return;
const plugin = new VConsole.VConsolePlugin("graphics-info", "🎨 Graphics Info");
plugin.on('renderTab', async function(callback) {
let html = ``;
// General GPU Info Table
const generalInfo = getGeneralGPUInfo();
if (generalInfo.length > 0) {
html += `
General GPU Info
`;
html += createTable(generalInfo, false);
}
// WebGL Info Table
const webglInfo = getWebGLDetailedInfo();
if (webglInfo.length > 0) {
html += `WebGL
`;
html += createTable(webglInfo, false);
}
// WebGL 2 Features Table
const webgl2Features = getWebGL2FeaturesTable();
if (webgl2Features.length > 0) {
html += `WebGL 2 Features
`;
html += createTable(webgl2Features, false);
}
// WebGL Limits Table
const webglLimits = getWebGLLimitsTable();
if (webglLimits.length > 0) {
html += `WebGL Limits
`;
html += createTable(webglLimits, false);
}
// Texture Formats Table
const textureFormats = getTextureFormatsTable();
if (textureFormats.length > 0) {
html += `Texture Formats
`;
html += createTable(textureFormats, false);
}
// WebGPU Info Table
const webgpuInfo = await getWebGPUInfoTable();
if (webgpuInfo.length > 0) {
html += `WebGPU
`;
html += createTable(webgpuInfo, false);
}
// Safari specific GPU info
if (DeviceUtilities.isSafari()) {
const safariGPUInfo = getSafariGPUInfo();
if (safariGPUInfo.length > 0) {
html += `Safari GPU Info
`;
html += createTable(safariGPUInfo, false);
}
}
html += "";
callback(html);
});
return plugin;
}
function getGeneralGPUInfo(): Array<{label: string, value: string}> {
const info: Array<{label: string, value: string}> = [];
// Display and window info
const dpr = window.devicePixelRatio;
info.push({ label: "Device Pixel Ratio", value: dpr.toString() });
info.push({ label: "Width (px)", value: (window.innerWidth * dpr).toString() });
info.push({ label: "Height (px)", value: (window.innerHeight * dpr).toString() });
// Estimated physical screen size in cm (96 DPI desktop, 150 DPI mobile)
const isMobile = DeviceUtilities.isMobileDevice();
const estimatedDPI = isMobile ? 150 : 96;
const widthInches = screen.width / estimatedDPI;
const heightInches = screen.height / estimatedDPI;
const widthCm = widthInches * 2.54;
const heightCm = heightInches * 2.54;
info.push({ label: "Estimated Width (cm)", value: widthCm.toFixed(1) });
info.push({ label: "Estimated Height (cm)", value: heightCm.toFixed(1) });
const webglInfo = getWebGLInfo();
if (webglInfo) {
info.push({ label: "GPU", value: webglInfo.renderer });
info.push({ label: "Driver", value: webglInfo.vendor });
info.push({ label: "ANGLE", value: webglInfo.angle || "Not detected" });
// Enhanced GPU parsing
const gpuDetails = parseGPUDetails(webglInfo.renderer);
if (gpuDetails) {
if (gpuDetails.manufacturer) info.push({ label: "Manufacturer", value: gpuDetails.manufacturer });
if (gpuDetails.cardVersion) info.push({ label: "Card Version", value: gpuDetails.cardVersion });
if (gpuDetails.brand) info.push({ label: "Brand", value: gpuDetails.brand });
info.push({ label: "Integrated", value: gpuDetails.integrated ? "Yes" : "No" });
if (gpuDetails.layer) info.push({ label: "WebGL Layer", value: gpuDetails.layer });
}
}
return info;
}
function parseGPUDetails(renderer: string): {manufacturer?: string, cardVersion?: string, brand?: string, integrated?: boolean, layer?: string, card?: string} | null {
if (!renderer) return null;
const extractValue = (reg: RegExp, str: string) => {
const matches = str.match(reg);
return matches && matches[0];
};
const layer = extractValue(/(ANGLE)/g, renderer) || undefined;
const card = extractValue(/((NVIDIA|AMD|Intel)[^\d]*[^\s]+)/, renderer) || renderer;
const tokens = card.split(' ');
tokens.shift();
const manufacturer = extractValue(/(NVIDIA|AMD|Intel)/g, card) || undefined;
const cardVersion = tokens.length > 0 ? tokens.pop() : undefined;
const brand = tokens.length > 0 ? tokens.join(' ') : undefined;
const integrated = manufacturer === 'Intel';
return {
manufacturer,
cardVersion,
brand,
integrated,
layer,
card
};
}
function getWebGLDetailedInfo(): Array<{label: string, value: string}> {
const info: Array<{label: string, value: string}> = [];
const webglInfo = getWebGLInfo();
if (webglInfo) {
info.push({ label: "📊 WebGL Version", value: webglInfo.version });
info.push({ label: "🎮 WebGL 2 Available", value: getWebGL2Support() });
}
return info;
}
function getWebGL2FeaturesTable(): Array<{label: string, value: string}> {
const features: Array<{label: string, value: string}> = [];
try {
const gl = probeGl2();
if (!gl) return features;
// Check for important WebGL 2 features
features.push({ label: "Float Color Buffer", value: gl.getExtension('EXT_color_buffer_float') ? "✅" : "❌" });
features.push({ label: "Anisotropic Filtering", value: gl.getExtension('EXT_texture_filter_anisotropic') ? "✅" : "❌" });
features.push({ label: "Float Texture Linear", value: gl.getExtension('OES_texture_float_linear') ? "✅" : "❌" });
features.push({ label: "S3TC Compression", value: gl.getExtension('WEBGL_compressed_texture_s3tc') ? "✅" : "❌" });
features.push({ label: "ETC Compression", value: gl.getExtension('WEBGL_compressed_texture_etc') ? "✅" : "❌" });
features.push({ label: "PVRTC Compression", value: gl.getExtension('WEBGL_compressed_texture_pvrtc') ? "✅" : "❌" });
features.push({ label: "ASTC Compression", value: gl.getExtension('WEBGL_compressed_texture_astc') ? "✅" : "❌" });
} catch (e) {
// WebGL2 not supported
}
return features;
}
function getWebGLLimitsTable(): Array<{label: string, value: string}> {
const limits: Array<{label: string, value: string}> = [];
try {
// Try WebGL 2 first, fall back to WebGL 1
const gl = probeGlAny();
if (!gl) return limits;
const isWebGL2 = gl instanceof WebGL2RenderingContext;
limits.push({ label: "📏 Max Texture Size", value: gl.getParameter(gl.MAX_TEXTURE_SIZE).toString() });
limits.push({ label: "🎨 Max Renderbuffer Size", value: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE).toString() });
limits.push({ label: "🔗 Max Vertex Attribs", value: gl.getParameter(gl.MAX_VERTEX_ATTRIBS).toString() });
limits.push({ label: "🎯 Max Texture Units", value: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS).toString() });
if (isWebGL2) {
limits.push({ label: "⚡ Max Samples", value: gl.getParameter(gl.MAX_SAMPLES).toString() });
limits.push({ label: "🔄 Max Uniform Buffer Bindings", value: gl.getParameter(gl.MAX_UNIFORM_BUFFER_BINDINGS).toString() });
limits.push({ label: "📐 Max 3D Texture Size", value: gl.getParameter(gl.MAX_3D_TEXTURE_SIZE).toString() });
}
} catch (e) {
// WebGL not available
}
return limits;
}
function getTextureFormatsTable(): Array<{label: string, value: string}> {
const formats: Array<{label: string, value: string}> = [];
try {
// WebGL 1 texture formats
const gl1 = probeGl1();
if (gl1) {
formats.push({ label: "WebGL 1 RGBA", value: "✅" });
formats.push({ label: "WebGL 1 RGB", value: "✅" });
}
// WebGL 2 texture formats
const gl2 = probeGl2();
if (gl2) {
formats.push({ label: "WebGL 2 RGBA32F", value: gl2.getExtension('EXT_color_buffer_float') ? "✅" : "❌" });
formats.push({ label: "WebGL 2 RGB32F", value: gl2.getExtension('EXT_color_buffer_float') ? "✅" : "❌" });
formats.push({ label: "WebGL 2 R11F_G11F_B10F", value: "✅" });
formats.push({ label: "WebGL 2 RGB565", value: "✅" });
formats.push({ label: "WebGL 2 RGB5_A1", value: "✅" });
formats.push({ label: "WebGL 2 RGBA4444", value: "✅" });
}
} catch (e) {
// WebGL not available
}
return formats;
}
async function getWebGPUInfoTable(): Promise> {
const info: Array<{label: string, value: string}> = [];
if (!('gpu' in navigator)) {
info.push({ label: "🚀 WebGPU Support", value: "❌ Not supported" });
return info;
}
info.push({ label: "🚀 WebGPU Support", value: "✅ Supported" });
try {
const adapter = await (navigator as any).gpu.requestAdapter();
if (!adapter) {
info.push({ label: "🎯 Adapter", value: "No adapter available" });
return info;
}
info.push({ label: "🎯 Adapter", value: adapter.name || "Unknown Adapter" });
const device = await adapter.requestDevice();
info.push({ label: "🔧 Device", value: device.label || "WebGPU Device" });
// WebGPU Limits
info.push({ label: "📏 Max Texture 2D", value: device.limits.maxTextureDimension2D.toString() });
info.push({ label: "📐 Max Texture 3D", value: device.limits.maxTextureDimension3D.toString() });
info.push({ label: "📊 Max Texture Array Layers", value: device.limits.maxTextureArrayLayers.toString() });
info.push({ label: "💾 Max Buffer Size", value: `${(device.limits.maxBufferSize / 1024 / 1024).toFixed(1)}MB` });
info.push({ label: "🔗 Max Bind Groups", value: device.limits.maxBindGroups.toString() });
} catch (e) {
info.push({ label: "❌ Error", value: (e as Error).message });
}
return info;
}
function getWebGLInfo(): {renderer: string, vendor: string, version: string, angle?: string} | null {
try {
// Try WebGL 2 first, fall back to WebGL 1
const gl = probeGlAny();
if (!gl) return null;
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : gl.getParameter(gl.RENDERER);
const vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : gl.getParameter(gl.VENDOR);
const version = gl.getParameter(gl.VERSION);
// Try to detect ANGLE
let angle = undefined;
if (renderer && renderer.includes('ANGLE')) {
const angleMatch = renderer.match(/ANGLE \(([^)]+)\)/);
if (angleMatch) {
angle = angleMatch[1];
}
}
return { renderer, vendor, version, angle };
} catch (e) {
return null;
}
}
function getSafariGPUInfo(): Array<{label: string, value: string}> {
const info: Array<{label: string, value: string}> = [];
// Try to get Safari-specific GPU info
try {
const gl = probeGl1();
if (gl) {
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
if (debugInfo) {
const renderer = gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
if (renderer && renderer.includes('Apple')) {
info.push({ label: "🍎 Apple GPU", value: renderer });
}
}
}
} catch (e) {
// Ignore errors
}
// Check for WebGL extensions specific to Safari
try {
const gl = probeGl1();
if (gl) {
const extensions = gl.getSupportedExtensions() || [];
if (extensions.includes('WEBGL_compressed_texture_pvrtc')) {
info.push({ label: "🗜️ PVRTC Support", value: "✅" });
}
}
} catch (e) {
// Ignore errors
}
return info;
}
function getDeviceType(): string {
if (DeviceUtilities.isQuest()) return "Meta Quest";
if (DeviceUtilities.isVisionOS()) return "Vision Pro";
if (DeviceUtilities.isiOS()) {
if (DeviceUtilities.isiPad()) return "iPad";
return "iPhone/iPod";
}
if (DeviceUtilities.isAndroidDevice()) return "Android Device";
if (DeviceUtilities.isMozillaXR()) return "Mozilla XR Browser";
if (DeviceUtilities.isNeedleAppClip()) return "Needle App Clip";
if (DeviceUtilities.isMacOS()) return "Mac";
if (DeviceUtilities.isDesktop()) return "Desktop PC";
return "Unknown Device";
}
function getConsoleSwitchButton(): HTMLElement | null {
const el = document.querySelector("#__vconsole .vc-switch");
if (el) return el as HTMLElement;
return null;
}
function getConsoleElement(): HTMLElement | null {
const el = document.querySelector("#__vconsole");
if (el) return el as HTMLElement;
return null;
}