255 lines
11 KiB
JavaScript
255 lines
11 KiB
JavaScript
const Lang = imports.lang;
|
|
const St = imports.gi.St;
|
|
const Clutter = imports.gi.Clutter;
|
|
const Gio = imports.gi.Gio;
|
|
const GLib = imports.gi.GLib;
|
|
const GObject = imports.gi.GObject;
|
|
const Cogl = imports.gi.Cogl;
|
|
const Shell = imports.gi.Shell;
|
|
const Meta = imports.gi.Meta;
|
|
|
|
const ExtensionUtils = imports.misc.extensionUtils;
|
|
const Me = ExtensionUtils.getCurrentExtension();
|
|
const Main = imports.ui.main;
|
|
const PanelMenu = imports.ui.panelMenu;
|
|
|
|
const UINT8_SIZE = 1;
|
|
const BOOL_SIZE = UINT8_SIZE;
|
|
const UINT_SIZE = 4;
|
|
const FLOAT_SIZE = 4;
|
|
|
|
// end offset, exclusive
|
|
const DATA_VIEW_INFO_OFFSET_INDEX = 0;
|
|
const DATA_VIEW_INFO_SIZE_INDEX = 1;
|
|
const DATA_VIEW_INFO_COUNT_INDEX = 1;
|
|
function dataViewEnd(dataViewInfo) {
|
|
return dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX] + dataViewInfo[DATA_VIEW_INFO_SIZE_INDEX] * dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX];
|
|
}
|
|
|
|
// the driver should be using the same data layout version
|
|
const DATA_LAYOUT_VERSION = 1;
|
|
|
|
// DataView info: [offset, size, count]
|
|
const VERSION = [0, UINT8_SIZE, 1];
|
|
const ENABLED = [dataViewEnd(VERSION), BOOL_SIZE, 1];
|
|
const EPOCH_SEC_OFFSET = [dataViewEnd(VERSION), UINT_SIZE, 1];
|
|
const LOOK_AHEAD_CFG = [dataViewEnd(EPOCH_SEC_OFFSET), FLOAT_SIZE, 4];
|
|
const DISPLAY_RES = [dataViewEnd(LOOK_AHEAD_CFG), UINT_SIZE, 2];
|
|
const DISPLAY_FOV = [dataViewEnd(DISPLAY_RES), FLOAT_SIZE, 1];
|
|
const DISPLAY_ZOOM = [dataViewEnd(DISPLAY_FOV), FLOAT_SIZE, 1];
|
|
const DISPLAY_NORTH_OFFSET = [dataViewEnd(DISPLAY_ZOOM), FLOAT_SIZE, 1];
|
|
const LENS_DISTANCE_RATIO = [dataViewEnd(DISPLAY_NORTH_OFFSET), FLOAT_SIZE, 1];
|
|
const SBS_ENABLED = [dataViewEnd(LENS_DISTANCE_RATIO), BOOL_SIZE, 1];
|
|
const SBS_CONTENT = [dataViewEnd(SBS_ENABLED), BOOL_SIZE, 1];
|
|
const SBS_MODE_STRETCHED = [dataViewEnd(SBS_CONTENT), BOOL_SIZE, 1];
|
|
const CUSTOM_BANNER_ENABLED = [dataViewEnd(SBS_MODE_STRETCHED), BOOL_SIZE, 1];
|
|
const IMU_QUAT_DATA = [dataViewEnd(CUSTOM_BANNER_ENABLED), FLOAT_SIZE, 16];
|
|
|
|
// cached after first retrieval
|
|
const shaderUniformLocations = {
|
|
'enabled': null,
|
|
'imu_quat_data': null,
|
|
'look_ahead_cfg': null,
|
|
'stage_aspect_ratio': null,
|
|
'display_aspect_ratio': null,
|
|
'display_zoom': null,
|
|
'display_north_offset': null,
|
|
'lens_distance_ratio': null,
|
|
'sbs_enabled': null,
|
|
'sbs_content': null,
|
|
'sbs_mode_stretched': null,
|
|
'custom_banner_enabled': null,
|
|
'half_fov_z_rads': null,
|
|
'half_fov_y_rads': null,
|
|
'screen_distance': null
|
|
};
|
|
|
|
function dataViewUint8(dataView, dataViewInfo) {
|
|
return dataView.getUint8(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX]);
|
|
}
|
|
|
|
function dataViewUint(dataView, dataViewInfo) {
|
|
return dataView.getUint32(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX], true);
|
|
}
|
|
|
|
function dataViewUintArray(dataView, dataViewInfo) {
|
|
const uintArray = []
|
|
const offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX];
|
|
for (let i = 0; i < dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; i++) {
|
|
uintArray.push(dataView.getUint32(offset, true));
|
|
offset += UINT_SIZE;
|
|
}
|
|
return uintArray;
|
|
}
|
|
|
|
function dataViewFloat(dataView, dataViewInfo) {
|
|
return dataView.getFloat32(dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX], true);
|
|
}
|
|
|
|
function dataViewFloatArray(dataView, dataViewInfo) {
|
|
const floatArray = []
|
|
const offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX];
|
|
for (let i = 0; i < dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX]; i++) {
|
|
floatArray.push(dataView.getFloat32(offset, true));
|
|
offset += FLOAT_SIZE;
|
|
}
|
|
return floatArray;
|
|
}
|
|
|
|
|
|
function getShaderSource(path) {
|
|
const file = Gio.file_new_for_path(path);
|
|
const data = file.load_contents(null);
|
|
|
|
// version string helps with linting, but GNOME extension doesn't like it, so remove it if it's there
|
|
return data[1].toString().replace(/^#version .*$/gm, '') + '\n';
|
|
}
|
|
|
|
function transferUniformBoolean(effect, locationName, dataView, dataViewInfo) {
|
|
// GLSL bool is a float under the hood, evaluates false if 0 or 0.0, true otherwise
|
|
effect.set_uniform_float(locationName, 1, [dataViewUint8(dataView, dataViewInfo)]);
|
|
}
|
|
|
|
function setUniformFloat(effect, locationName, dataViewInfo, value) {
|
|
effect.set_uniform_float(shaderUniformLocations[locationName], dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX], value);
|
|
}
|
|
|
|
function transferUniformFloat(effect, locationName, dataView, dataViewInfo) {
|
|
setUniformFloat(effect, locationName, dataViewInfo, dataViewFloatArray(dataView, dataViewInfo));
|
|
|
|
function setUniformMatrix(effect, locationName, components, dataView, dataViewInfo) {
|
|
const numValues = dataViewInfo[DATA_VIEW_INFO_COUNT_INDEX];
|
|
if (numValues / componenents !== components) {
|
|
throw new Error('Invalid matrix size');
|
|
}
|
|
|
|
const floatArray = [].fill(0, 0, numValues);
|
|
const offset = dataViewInfo[DATA_VIEW_INFO_OFFSET_INDEX];
|
|
for (let i = 0; i < numValues; i++) {
|
|
// GLSL uses column-major order, so we need to transpose the matrix
|
|
const row = i % components;
|
|
const column = Math.floor(i / components);
|
|
|
|
floatArray[row * components + column] = dataView.getFloat32(offset, true);
|
|
offset += FLOAT_SIZE;
|
|
}
|
|
effect.set_uniform_matrix(shaderUniformLocations[locationName], true, components, floatArray);
|
|
}
|
|
|
|
function getEpochSec() {
|
|
return Math.floor(Date.now() / 1000);
|
|
}
|
|
|
|
function degreeToRadian(degree) {
|
|
return degree * Math.PI / 180;
|
|
}
|
|
|
|
// config doesn't change frequently, so we'll set these all at once, periodically
|
|
function setConfigUniformVarables(effect, dataView) {
|
|
const version = dataViewUint8(dataView, VERSION);
|
|
const date = dataViewUint(dataView, EPOCH_SEC_OFFSET);
|
|
const validKeepalive = Math.abs(getEpochSec() - date) < 5;
|
|
const imuData = dataViewFloatArray(dataView, IMU_QUAT_DATA);
|
|
const imuResetState = imuData[0] === 0.0 && imuData[1] === 0.0 && imuData[2] === 0.0 && imuData[3] === 1.0;
|
|
const enabled = dataViewUint8(dataView, ENABLED) !== 0 && version === DATA_LAYOUT_VERSION && validKeepalive && !imuResetState;
|
|
if (enabled) {
|
|
const displayRes = dataViewUintArray(dataView, DISPLAY_RES);
|
|
const displayFov = dataViewFloat(dataView, DISPLAY_FOV);
|
|
const lensDistanceRatio = dataViewFloat(dataView, LENS_DISTANCE_RATIO);
|
|
|
|
// compute these values once, they only change when the XR device changes
|
|
const displayAspectRatio = displayRes[0] / displayRes[1];
|
|
const stageAspectRatio = global.stage.get_width() / global.stage.get_height();
|
|
const diagToVertRatio = Math.sqrt(Math.pow(stageAspectRatio, 2) + 1);
|
|
const halfFovZRads = degreeToRadian(displayFov / diagToVertRatio) / 2;
|
|
const halfFovYRads = halfFovZRads * stageAspectRatio;
|
|
const screenDistance = 1.0 - lensDistanceRatio;
|
|
|
|
// all these values are passed directly to the shader, unmodified
|
|
transferUniformFloat(effect, 'imu_quat_data', dataView, IMU_QUAT_DATA);
|
|
transferUniformFloat(effect, 'look_ahead_cfg', dataView, LOOK_AHEAD_CFG);
|
|
transferUniformFloat(effect, 'display_zoom', dataView, DISPLAY_ZOOM);
|
|
transferUniformFloat(effect, 'display_north_offset', dataView, DISPLAY_NORTH_OFFSET);
|
|
transferUniformFloat(effect, 'lens_distance_ratio', dataView, LENS_DISTANCE_RATIO);
|
|
transferUniformBoolean(effect, 'sbs_enabled', dataView, SBS_ENABLED);
|
|
transferUniformBoolean(effect, 'sbs_content', dataView, SBS_CONTENT);
|
|
transferUniformBoolean(effect, 'sbs_mode_stretched', dataView, SBS_MODE_STRETCHED);
|
|
transferUniformBoolean(effect, 'custom_banner_enabled', dataView, CUSTOM_BANNER_ENABLED);
|
|
|
|
// no dataViewInfo, so we set these manually
|
|
effect.set_uniform_float(shaderUniformLocations['stage_aspect_ratio'], 1, [stageAspectRatio]);
|
|
effect.set_uniform_float(shaderUniformLocations['display_aspect_ratio'], 1, [displayAspectRatio]);
|
|
effect.set_uniform_float(shaderUniformLocations['half_fov_z_rads'], 1, [halfFovZRads]);
|
|
effect.set_uniform_float(shaderUniformLocations['half_fov_y_rads'], 1, [halfFovYRads]);
|
|
effect.set_uniform_float(shaderUniformLocations['screen_distance'], 1, [screenDistance]);
|
|
}
|
|
setUniformBoolean(effect, shaderUniformLocations['enabled'], ENABLED, enabled);
|
|
}
|
|
|
|
class Extension {
|
|
enable() {
|
|
var XREffect = GObject.registerClass({}, class XREffect extends Shell.GLSLEffect {
|
|
vfunc_build_pipeline() {
|
|
// Shell.GLSLEffect requires the declarations and the main source code as separate
|
|
// strings. As it's more convenient to store the in one GLSL file, we use a regex
|
|
// here to split the source code in two parts.
|
|
const code = getShaderSource('/home/wayne/IdeaProjects/nreal/breezy-desktop/gnome/breezydesktop@org.xronlinux/IMUAdjust.frag');
|
|
|
|
const main = 'PS_IMU_Transform(vec4(0, 0, 0, 0), cogl_tex_coord_in[0].xy, cogl_color_out);';
|
|
this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, code, main, false);
|
|
}
|
|
|
|
// TODO - read IMU data and update uniform variables
|
|
vfunc_paint_target(node, paintContext) {
|
|
|
|
const data = this._shared_mem_file.load_contents(null);
|
|
if (data[0]) {
|
|
const buffer = new Uint8Array(data[1]).buffer;
|
|
var dataView = new DataView(buffer);
|
|
var repaintNeeded = false;
|
|
if (!this._initialized) {
|
|
this.set_uniform_float(this.get_uniform_location('uDesktopTexture'), 1, [0]);
|
|
this._shared_mem_file = Gio.file_new_for_path("/dev/shm/imu_data");
|
|
|
|
// iterate over shaderUniformLocations keys and set the uniform locations
|
|
for (let key in shaderUniformLocations) {
|
|
shaderUniformLocations[key] = this.get_uniform_location(key);
|
|
}
|
|
|
|
setConfigUniformVarables(this, dataView);
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000/50, () => {
|
|
repaintNeeded = true;
|
|
this.queue_repaint();
|
|
return GLib.SOURCE_CONTINUE;
|
|
});
|
|
|
|
GLib.timeout_add(GLib.PRIORITY_DEFAULT, 250, () => {
|
|
setConfigUniformVarables(this, dataView);
|
|
return GLib.SOURCE_CONTINUE;
|
|
});
|
|
Meta.CursorTracker.get_for_display(global.display).set_pointer_visible(true);
|
|
console.log(`is_rendering_hardware_accelerated: ${Meta.CursorTracker.get_for_display(global.display).backend.is_rendering_hardware_accelerated()}`);
|
|
|
|
this._initialized = true;
|
|
}
|
|
|
|
transferUniformFloat(this, 'imu_quat_data', dataView, IMU_QUAT_DATA);
|
|
|
|
// if (repaintNeeded) {
|
|
super.vfunc_paint_target(node, paintContext);
|
|
// }
|
|
}
|
|
}
|
|
});
|
|
|
|
global.stage.add_effect(new XREffect());
|
|
}
|
|
|
|
disable() {
|
|
}
|
|
}
|
|
|
|
function init() {
|
|
return new Extension();
|
|
} |