From eb1c027c30f9725c249a5118060a22a8867d63a3 Mon Sep 17 00:00:00 2001 From: redphx <96280+redphx@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:56:33 +0700 Subject: [PATCH] Optimize WebGPU --- dist/better-xcloud.user.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/dist/better-xcloud.user.js b/dist/better-xcloud.user.js index 780d9ff..cdbb92a 100755 --- a/dist/better-xcloud.user.js +++ b/dist/better-xcloud.user.js @@ -77,7 +77,7 @@ class MkbMappingPresetsTable extends BasePresetsTable {static instance;static ge class GameSettingsStorage extends BaseSettingsStorage {constructor(id) {super(`${"BetterXcloud.Stream"}.${id}`, StreamSettingsStorage.DEFINITIONS);}isEmpty() {return Object.keys(this.settings).length === 0;}} class ControllerCustomizationsTable extends BasePresetsTable {static instance;static getInstance = () => ControllerCustomizationsTable.instance ?? (ControllerCustomizationsTable.instance = new ControllerCustomizationsTable(LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS));TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_CUSTOMIZATIONS;DEFAULT_PRESETS = {[-1]: {id: -1,name: "ABXY ⇄ BAYX",data: {mapping: {0: 1,1: 0,2: 3,3: 2},settings: {leftStickDeadzone: [0, 100],rightStickDeadzone: [0, 100],leftTriggerRange: [0, 100],rightTriggerRange: [0, 100],vibrationIntensity: 100}}}};BLANK_PRESET_DATA = {mapping: {},settings: {leftTriggerRange: [0, 100],rightTriggerRange: [0, 100],leftStickDeadzone: [0, 100],rightStickDeadzone: [0, 100],vibrationIntensity: 100}};DEFAULT_PRESET_ID = 0;} class ControllerShortcutsTable extends BasePresetsTable {static instance;static getInstance = () => ControllerShortcutsTable.instance ?? (ControllerShortcutsTable.instance = new ControllerShortcutsTable);LOG_TAG = "ControllerShortcutsTable";TABLE_PRESETS = LocalDb.TABLE_CONTROLLER_SHORTCUTS;DEFAULT_PRESETS = {[-1]: {id: -1,name: "Type A",data: {mapping: {3: AppInterface ? "device.volume.inc" : "stream.volume.inc",0: AppInterface ? "device.volume.dec" : "stream.volume.dec",2: "stream.stats.toggle",1: AppInterface ? "device.sound.toggle" : "stream.sound.toggle",5: "stream.screenshot.capture",9: "stream.menu.show"}}},[-2]: {id: -2,name: "Type B",data: {mapping: {12: AppInterface ? "device.volume.inc" : "stream.volume.inc",13: AppInterface ? "device.volume.dec" : "stream.volume.dec",15: "stream.stats.toggle",14: AppInterface ? "device.sound.toggle" : "stream.sound.toggle",4: "stream.screenshot.capture",8: "stream.menu.show"}}}};BLANK_PRESET_DATA = {mapping: {}};DEFAULT_PRESET_ID = -1;constructor() {super(LocalDb.TABLE_CONTROLLER_SHORTCUTS);BxLogger.info(this.LOG_TAG, "constructor()");}} -class StreamSettingsStorage extends BaseSettingsStorage {static DEFINITIONS = {"deviceVibration.mode": {requiredVariants: "full",label: t("device-vibration"),default: "off",options: {off: t("off"),on: t("on"),auto: t("device-vibration-not-using-gamepad")}},"deviceVibration.intensity": {requiredVariants: "full",label: t("vibration-intensity"),default: 50,min: 10,max: 100,params: {steps: 10,suffix: "%",exactTicks: 20}},"controller.pollingRate": {requiredVariants: "full",label: t("polling-rate"),default: 4,min: 4,max: 60,params: {steps: 4,exactTicks: 20,reverse: !0,customTextValue(value) {value = parseInt(value);let text = +(1000 / value).toFixed(2) + " Hz";if (value === 4) text = `${text} (${t("default")})`;return text;}}},"controller.settings": {default: {}},"nativeMkb.scroll.sensitivityX": {requiredVariants: "full",label: t("horizontal-scroll-sensitivity"),default: 0,min: 0,max: 1e4,params: {steps: 10,exactTicks: 2000,customTextValue: (value) => {if (!value) return t("default");return (value / 100).toFixed(1) + "x";}}},"nativeMkb.scroll.sensitivityY": {requiredVariants: "full",label: t("vertical-scroll-sensitivity"),default: 0,min: 0,max: 1e4,params: {steps: 10,exactTicks: 2000,customTextValue: (value) => {if (!value) return t("default");return (value / 100).toFixed(1) + "x";}}},"mkb.p1.preset.mappingId": {requiredVariants: "full",default: -1},"mkb.p1.slot": {requiredVariants: "full",default: 1,min: 1,max: 4,params: {hideSlider: !0}},"mkb.p2.preset.mappingId": {requiredVariants: "full",default: 0},"mkb.p2.slot": {requiredVariants: "full",default: 0,min: 0,max: 4,params: {hideSlider: !0,customTextValue(value) {return value = parseInt(value), value === 0 ? t("off") : value.toString();}}},"keyboardShortcuts.preset.inGameId": {requiredVariants: "full",default: -1},"video.player.type": {label: t("renderer"),default: "default",options: {default: t("default"),webgl2: t("webgl2"),webgpu: t("webgpu")},suggest: {lowest: "default",highest: "webgl2"},ready: (setting) => {if (!navigator.gpu) delete setting.options["webgpu"];}},"video.processing": {label: t("clarity-boost"),default: "usm",options: {usm: t("unsharp-masking"),cas: t("amd-fidelity-cas")},suggest: {lowest: "usm",highest: "cas"}},"video.player.powerPreference": {label: t("renderer-configuration"),default: "default",options: {default: t("default"),"low-power": t("battery-saving"),"high-performance": t("high-performance")},suggest: {highest: "low-power"}},"video.maxFps": {label: t("limit-fps"),default: 60,min: 10,max: 60,params: {steps: 10,exactTicks: 10,customTextValue: (value) => {return value = parseInt(value), value === 60 ? t("unlimited") : value + "fps";}}},"video.processing.sharpness": {label: t("sharpness"),default: 0,min: 0,max: 10,params: {exactTicks: 2,customTextValue: (value) => {return value = parseInt(value), value === 0 ? t("off") : value.toString();}},suggest: {lowest: 0,highest: 2}},"video.ratio": {label: t("aspect-ratio"),note: STATES.browser.capabilities.touch ? t("aspect-ratio-note") : void 0,default: "16:9",options: {"16:9": `16:9 (${t("default")})`,"18:9": "18:9","21:9": "21:9","16:10": "16:10","4:3": "4:3",fill: t("stretch")}},"video.position": {label: t("position"),note: STATES.browser.capabilities.touch ? t("aspect-ratio-note") : void 0,default: "center",options: {top: t("top"),"top-half": t("top-half"),center: `${t("center")} (${t("default")})`,"bottom-half": t("bottom-half"),bottom: t("bottom")}},"video.saturation": {label: t("saturation"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"video.contrast": {label: t("contrast"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"video.brightness": {label: t("brightness"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"audio.volume": {label: t("volume"),default: 100,min: 0,max: 600,params: {steps: 10,suffix: "%",ticks: 100}},"stats.items": {label: t("stats"),default: ["ping", "fps", "btr", "dt", "pl", "fl"],multipleOptions: {time: t("clock"),play: t("playtime"),batt: t("battery"),ping: t("stat-ping"),jit: t("jitter"),fps: t("stat-fps"),btr: t("stat-bitrate"),dt: t("stat-decode-time"),pl: t("stat-packets-lost"),fl: t("stat-frames-lost"),dl: t("downloaded"),ul: t("uploaded")},params: {size: 0},ready: (setting) => {let multipleOptions = setting.multipleOptions;if (!STATES.browser.capabilities.batteryApi) delete multipleOptions["batt"];for (let key in multipleOptions)multipleOptions[key] = key.toUpperCase() + ": " + multipleOptions[key];}},"stats.showWhenPlaying": {label: t("show-stats-on-startup"),default: !1},"stats.quickGlance.enabled": {label: "👀 " + t("enable-quick-glance-mode"),default: !0},"stats.position": {label: t("position"),default: "top-right",options: {"top-left": t("top-left"),"top-center": t("top-center"),"top-right": t("top-right")}},"stats.textSize": {label: t("text-size"),default: "0.9rem",options: {"0.9rem": t("small"),"1.0rem": t("normal"),"1.1rem": t("large")}},"stats.opacity.all": {label: t("opacity"),default: 80,min: 50,max: 100,params: {steps: 10,suffix: "%",ticks: 10}},"stats.opacity.background": {label: t("background-opacity"),default: 100,min: 0,max: 100,params: {steps: 10,suffix: "%",ticks: 10}},"stats.colors": {label: t("conditional-formatting"),default: !1},"localCoOp.enabled": {requiredVariants: "full",label: t("enable-local-co-op-support"),labelIcon: BxIcon.LOCAL_CO_OP,default: !1,note: () => CE("div", !1, CE("a", {href: "https://github.com/redphx/better-xcloud/discussions/275",target: "_blank"}, t("enable-local-co-op-support-note")), CE("br"), "⚠️ " + t("unexpected-behavior"))}};gameSettings = {};xboxTitleId = -1;constructor() {super("BetterXcloud.Stream", StreamSettingsStorage.DEFINITIONS);}setGameId(id) {this.xboxTitleId = id;}getGameSettings(id) {if (id > -1) {if (!this.gameSettings[id]) {let gameStorage = new GameSettingsStorage(id);this.gameSettings[id] = gameStorage;for (let key in gameStorage.settings)this.getSettingByGame(id, key);}return this.gameSettings[id];}return null;}getSetting(key, checkUnsupported) {return this.getSettingByGame(this.xboxTitleId, key, checkUnsupported);}getSettingByGame(id, key, checkUnsupported) {let gameSettings = this.getGameSettings(id);if (gameSettings?.hasSetting(key)) {let gameValue = gameSettings.getSetting(key, checkUnsupported), globalValue = super.getSetting(key, checkUnsupported);if (globalValue === gameValue) this.deleteSettingByGame(id, key), gameValue = globalValue;return gameValue;}return super.getSetting(key, checkUnsupported);}setSetting(key, value, origin) {return this.setSettingByGame(this.xboxTitleId, key, value, origin);}setSettingByGame(id, key, value, origin) {let gameSettings = this.getGameSettings(id);if (gameSettings) return BxLogger.info("setSettingByGame", id, key, value), gameSettings.setSetting(key, value, origin);return BxLogger.info("setSettingByGame", id, key, value), super.setSetting(key, value, origin);}deleteSettingByGame(id, key) {let gameSettings = this.getGameSettings(id);if (gameSettings) return gameSettings.deleteSetting(key);return !1;}hasGameSetting(id, key) {let gameSettings = this.getGameSettings(id);return !!(gameSettings && gameSettings.hasSetting(key));}getControllerSetting(gamepadId) {let controllerSetting = this.getSetting("controller.settings")[gamepadId];if (!controllerSetting) controllerSetting = {};if (!controllerSetting.hasOwnProperty("shortcutPresetId")) controllerSetting.shortcutPresetId = -1;if (!controllerSetting.hasOwnProperty("customizationPresetId")) controllerSetting.customizationPresetId = 0;return controllerSetting;}} +class StreamSettingsStorage extends BaseSettingsStorage {static DEFINITIONS = {"deviceVibration.mode": {requiredVariants: "full",label: t("device-vibration"),default: "off",options: {off: t("off"),on: t("on"),auto: t("device-vibration-not-using-gamepad")}},"deviceVibration.intensity": {requiredVariants: "full",label: t("vibration-intensity"),default: 50,min: 10,max: 100,params: {steps: 10,suffix: "%",exactTicks: 20}},"controller.pollingRate": {requiredVariants: "full",label: t("polling-rate"),default: 4,min: 4,max: 60,params: {steps: 4,exactTicks: 20,reverse: !0,customTextValue(value) {value = parseInt(value);let text = +(1000 / value).toFixed(2) + " Hz";if (value === 4) text = `${text} (${t("default")})`;return text;}}},"controller.settings": {default: {}},"nativeMkb.scroll.sensitivityX": {requiredVariants: "full",label: t("horizontal-scroll-sensitivity"),default: 0,min: 0,max: 1e4,params: {steps: 10,exactTicks: 2000,customTextValue: (value) => {if (!value) return t("default");return (value / 100).toFixed(1) + "x";}}},"nativeMkb.scroll.sensitivityY": {requiredVariants: "full",label: t("vertical-scroll-sensitivity"),default: 0,min: 0,max: 1e4,params: {steps: 10,exactTicks: 2000,customTextValue: (value) => {if (!value) return t("default");return (value / 100).toFixed(1) + "x";}}},"mkb.p1.preset.mappingId": {requiredVariants: "full",default: -1},"mkb.p1.slot": {requiredVariants: "full",default: 1,min: 1,max: 4,params: {hideSlider: !0}},"mkb.p2.preset.mappingId": {requiredVariants: "full",default: 0},"mkb.p2.slot": {requiredVariants: "full",default: 0,min: 0,max: 4,params: {hideSlider: !0,customTextValue(value) {return value = parseInt(value), value === 0 ? t("off") : value.toString();}}},"keyboardShortcuts.preset.inGameId": {requiredVariants: "full",default: -1},"video.player.type": {label: t("renderer"),default: "default",options: {default: t("default"),webgl2: t("webgl2"),webgpu: `${t("webgpu")} (${t("experimental")})`},suggest: {lowest: "default",highest: "webgl2"},ready: (setting) => {if (!navigator.gpu) delete setting.options["webgpu"];}},"video.processing": {label: t("clarity-boost"),default: "usm",options: {usm: t("unsharp-masking"),cas: t("amd-fidelity-cas")},suggest: {lowest: "usm",highest: "cas"}},"video.player.powerPreference": {label: t("renderer-configuration"),default: "default",options: {default: t("default"),"low-power": t("battery-saving"),"high-performance": t("high-performance")},suggest: {highest: "low-power"}},"video.maxFps": {label: t("limit-fps"),default: 60,min: 10,max: 60,params: {steps: 10,exactTicks: 10,customTextValue: (value) => {return value = parseInt(value), value === 60 ? t("unlimited") : value + "fps";}}},"video.processing.sharpness": {label: t("sharpness"),default: 0,min: 0,max: 10,params: {exactTicks: 2,customTextValue: (value) => {return value = parseInt(value), value === 0 ? t("off") : value.toString();}},suggest: {lowest: 0,highest: 2}},"video.ratio": {label: t("aspect-ratio"),note: STATES.browser.capabilities.touch ? t("aspect-ratio-note") : void 0,default: "16:9",options: {"16:9": `16:9 (${t("default")})`,"18:9": "18:9","21:9": "21:9","16:10": "16:10","4:3": "4:3",fill: t("stretch")}},"video.position": {label: t("position"),note: STATES.browser.capabilities.touch ? t("aspect-ratio-note") : void 0,default: "center",options: {top: t("top"),"top-half": t("top-half"),center: `${t("center")} (${t("default")})`,"bottom-half": t("bottom-half"),bottom: t("bottom")}},"video.saturation": {label: t("saturation"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"video.contrast": {label: t("contrast"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"video.brightness": {label: t("brightness"),default: 100,min: 50,max: 150,params: {suffix: "%",ticks: 25}},"audio.volume": {label: t("volume"),default: 100,min: 0,max: 600,params: {steps: 10,suffix: "%",ticks: 100}},"stats.items": {label: t("stats"),default: ["ping", "fps", "btr", "dt", "pl", "fl"],multipleOptions: {time: t("clock"),play: t("playtime"),batt: t("battery"),ping: t("stat-ping"),jit: t("jitter"),fps: t("stat-fps"),btr: t("stat-bitrate"),dt: t("stat-decode-time"),pl: t("stat-packets-lost"),fl: t("stat-frames-lost"),dl: t("downloaded"),ul: t("uploaded")},params: {size: 0},ready: (setting) => {let multipleOptions = setting.multipleOptions;if (!STATES.browser.capabilities.batteryApi) delete multipleOptions["batt"];for (let key in multipleOptions)multipleOptions[key] = key.toUpperCase() + ": " + multipleOptions[key];}},"stats.showWhenPlaying": {label: t("show-stats-on-startup"),default: !1},"stats.quickGlance.enabled": {label: "👀 " + t("enable-quick-glance-mode"),default: !0},"stats.position": {label: t("position"),default: "top-right",options: {"top-left": t("top-left"),"top-center": t("top-center"),"top-right": t("top-right")}},"stats.textSize": {label: t("text-size"),default: "0.9rem",options: {"0.9rem": t("small"),"1.0rem": t("normal"),"1.1rem": t("large")}},"stats.opacity.all": {label: t("opacity"),default: 80,min: 50,max: 100,params: {steps: 10,suffix: "%",ticks: 10}},"stats.opacity.background": {label: t("background-opacity"),default: 100,min: 0,max: 100,params: {steps: 10,suffix: "%",ticks: 10}},"stats.colors": {label: t("conditional-formatting"),default: !1},"localCoOp.enabled": {requiredVariants: "full",label: t("enable-local-co-op-support"),labelIcon: BxIcon.LOCAL_CO_OP,default: !1,note: () => CE("div", !1, CE("a", {href: "https://github.com/redphx/better-xcloud/discussions/275",target: "_blank"}, t("enable-local-co-op-support-note")), CE("br"), "⚠️ " + t("unexpected-behavior"))}};gameSettings = {};xboxTitleId = -1;constructor() {super("BetterXcloud.Stream", StreamSettingsStorage.DEFINITIONS);}setGameId(id) {this.xboxTitleId = id;}getGameSettings(id) {if (id > -1) {if (!this.gameSettings[id]) {let gameStorage = new GameSettingsStorage(id);this.gameSettings[id] = gameStorage;for (let key in gameStorage.settings)this.getSettingByGame(id, key);}return this.gameSettings[id];}return null;}getSetting(key, checkUnsupported) {return this.getSettingByGame(this.xboxTitleId, key, checkUnsupported);}getSettingByGame(id, key, checkUnsupported) {let gameSettings = this.getGameSettings(id);if (gameSettings?.hasSetting(key)) {let gameValue = gameSettings.getSetting(key, checkUnsupported), globalValue = super.getSetting(key, checkUnsupported);if (globalValue === gameValue) this.deleteSettingByGame(id, key), gameValue = globalValue;return gameValue;}return super.getSetting(key, checkUnsupported);}setSetting(key, value, origin) {return this.setSettingByGame(this.xboxTitleId, key, value, origin);}setSettingByGame(id, key, value, origin) {let gameSettings = this.getGameSettings(id);if (gameSettings) return BxLogger.info("setSettingByGame", id, key, value), gameSettings.setSetting(key, value, origin);return BxLogger.info("setSettingByGame", id, key, value), super.setSetting(key, value, origin);}deleteSettingByGame(id, key) {let gameSettings = this.getGameSettings(id);if (gameSettings) return gameSettings.deleteSetting(key);return !1;}hasGameSetting(id, key) {let gameSettings = this.getGameSettings(id);return !!(gameSettings && gameSettings.hasSetting(key));}getControllerSetting(gamepadId) {let controllerSetting = this.getSetting("controller.settings")[gamepadId];if (!controllerSetting) controllerSetting = {};if (!controllerSetting.hasOwnProperty("shortcutPresetId")) controllerSetting.shortcutPresetId = -1;if (!controllerSetting.hasOwnProperty("customizationPresetId")) controllerSetting.customizationPresetId = 0;return controllerSetting;}} function migrateStreamSettings() {let storage = window.localStorage, globalSettings = JSON.parse(storage.getItem("BetterXcloud") || "{}"), streamSettings = JSON.parse(storage.getItem("BetterXcloud.Stream") || "{}"), modified2 = !1;for (let key in globalSettings)if (isStreamPref(key)) {if (!streamSettings.hasOwnProperty(key)) streamSettings[key] = globalSettings[key];delete globalSettings[key], modified2 = !0;}if (modified2) storage.setItem("BetterXcloud", JSON.stringify(globalSettings)), storage.setItem("BetterXcloud.Stream", JSON.stringify(streamSettings));} migrateStreamSettings(); var STORAGE = {Global: new GlobalSettingsStorage,Stream: new StreamSettingsStorage}, streamSettingsStorage = STORAGE.Stream, getStreamPrefDefinition = streamSettingsStorage.getDefinition.bind(streamSettingsStorage), getStreamPref = streamSettingsStorage.getSetting.bind(streamSettingsStorage), setStreamPref = streamSettingsStorage.setSetting.bind(streamSettingsStorage), getGamePref = streamSettingsStorage.getSettingByGame.bind(streamSettingsStorage), setGamePref = streamSettingsStorage.setSettingByGame.bind(streamSettingsStorage), setGameIdPref = streamSettingsStorage.setGameId.bind(streamSettingsStorage), hasGamePref = streamSettingsStorage.hasGameSetting.bind(streamSettingsStorage); @@ -321,7 +321,7 @@ struct VertexOutput { @builtin(position) position: vec4, @location(0) uv: vec2,}; @group(0) @binding(0) var ourSampler: sampler; -@group(0) @binding(1) var ourTexture: texture_2d; +@group(0) @binding(1) var ourTexture: texture_external; @group(0) @binding(2) var ourParams: Params; const FILTER_UNSHARP_MASKING: f32 = 1.0; const CAS_CONTRAST_PEAK: f32 = 0.8 * -3.0 + 8.0; @@ -340,14 +340,14 @@ fn luminance(color: vec3) -> f32 { return dot(color, vec3(0.299, 0.587, 0.114));} fn clarityBoost(coord: vec2, texSize: vec2, e: vec3) -> vec3 { let texelSize = 1.0 / texSize; -let a = textureSample(ourTexture, ourSampler, coord + texelSize * vec2(-1.0, 1.0)).rgb; -let b = textureSample(ourTexture, ourSampler, coord + texelSize * vec2( 0.0, 1.0)).rgb; -let c = textureSample(ourTexture, ourSampler, coord + texelSize * vec2( 1.0, 1.0)).rgb; -let d = textureSample(ourTexture, ourSampler, coord + texelSize * vec2(-1.0, 0.0)).rgb; -let f = textureSample(ourTexture, ourSampler, coord + texelSize * vec2( 1.0, 0.0)).rgb; -let g = textureSample(ourTexture, ourSampler, coord + texelSize * vec2(-1.0, -1.0)).rgb; -let h = textureSample(ourTexture, ourSampler, coord + texelSize * vec2( 0.0, -1.0)).rgb; -let i = textureSample(ourTexture, ourSampler, coord + texelSize * vec2( 1.0, -1.0)).rgb; +let a = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2(-1.0, 1.0)).rgb; +let b = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2( 0.0, 1.0)).rgb; +let c = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2( 1.0, 1.0)).rgb; +let d = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2(-1.0, 0.0)).rgb; +let f = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2( 1.0, 0.0)).rgb; +let g = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2(-1.0, -1.0)).rgb; +let h = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2( 0.0, -1.0)).rgb; +let i = textureSampleBaseClampToEdge(ourTexture, ourSampler, coord + texelSize * vec2( 1.0, -1.0)).rgb; if ourParams.filterId == FILTER_UNSHARP_MASKING { let gaussianBlur = (a + c + g + i) * 1.0 + (b + d + f + h) * 2.0 + e * 4.0; let blurred = gaussianBlur / 16.0; @@ -365,14 +365,14 @@ return mix(e, outColor, ourParams.sharpness / 2.0);} @fragment fn fs_main(input: VertexOutput) -> @location(0) vec4 { let texSize = vec2(textureDimensions(ourTexture)); -let center = textureSample(ourTexture, ourSampler, input.uv); +let center = textureSampleBaseClampToEdge(ourTexture, ourSampler, input.uv); var adjustedRgb = clarityBoost(input.uv, texSize, center.rgb); let gray = luminance(adjustedRgb); adjustedRgb = mix(vec3(gray), adjustedRgb, ourParams.saturation); adjustedRgb = (adjustedRgb - 0.5) * ourParams.contrast + 0.5; adjustedRgb *= ourParams.brightness; return vec4(adjustedRgb, 1.0);}`; -class WebGPUPlayer extends BaseCanvasPlayer {static device;context;texture;pipeline;sampler;bindGroup;boundDrawFrame;static async prepare() {if (!navigator.gpu) return;try {let adapter = await navigator.gpu.requestAdapter({powerPreference: "low-power"});WebGPUPlayer.device = await adapter?.requestDevice();} catch (ex) {alert(ex);}}constructor($video) {super("webgpu", $video, "WebGPUPlayer");this.boundDrawFrame = this.drawFrame.bind(this);}setupShaders() {if (this.context = this.$canvas.getContext("webgpu"), !this.context) {alert("Can't initiate context");return;}let format = navigator.gpu.getPreferredCanvasFormat();this.context.configure({device: WebGPUPlayer.device,format,alphaMode: "opaque"}), this.texture = WebGPUPlayer.device.createTexture({size: [this.$video.videoWidth, this.$video.videoHeight, 1],format: "rgba8unorm",usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT});let shaderModule = WebGPUPlayer.device.createShaderModule({ code: clarity_boost_default3 });this.pipeline = WebGPUPlayer.device.createRenderPipeline({layout: "auto",vertex: {module: shaderModule,entryPoint: "vs_main"},fragment: {module: shaderModule,entryPoint: "fs_main",targets: [{ format }]},primitive: { topology: "triangle-list" }}), this.sampler = WebGPUPlayer.device.createSampler({ magFilter: "linear", minFilter: "linear" }), this.updateCanvas();}prepareUniformBuffer(value, classType) {let uniform = new classType(value), uniformBuffer = WebGPUPlayer.device.createBuffer({size: uniform.byteLength,usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST});return WebGPUPlayer.device.queue.writeBuffer(uniformBuffer, 0, uniform), uniformBuffer;}updateCanvas() {let paramsBuffer = this.prepareUniformBuffer([this.toFilterId(this.options.processing),this.options.sharpness,this.options.brightness / 100,this.options.contrast / 100,this.options.saturation / 100], Float32Array);this.bindGroup = WebGPUPlayer.device.createBindGroup({layout: this.pipeline.getBindGroupLayout(0),entries: [{ binding: 0, resource: this.sampler },{ binding: 1, resource: this.texture.createView() },{ binding: 2, resource: { buffer: paramsBuffer } }]});}drawFrame() {if (this.isStopped) return;this.animFrameId = this.frameCallback(this.boundDrawFrame);let draw = !0;if (this.targetFps === 0) draw = !1;else if (this.targetFps < 60) {let currentTime = performance.now();if (currentTime - this.lastFrameTime < this.frameInterval) draw = !1;else this.lastFrameTime = currentTime;}if (draw) {let frame = new VideoFrame(this.$video);WebGPUPlayer.device.queue.copyExternalImageToTexture({ source: frame }, { texture: this.texture }, [this.$video.videoWidth, this.$video.videoHeight]), frame.close();let commandEncoder = WebGPUPlayer.device.createCommandEncoder(), passEncoder = commandEncoder.beginRenderPass({colorAttachments: [{view: this.context.getCurrentTexture().createView(),loadOp: "clear",storeOp: "store",clearValue: [0, 0, 0, 1]}]});passEncoder.setPipeline(this.pipeline), passEncoder.setBindGroup(0, this.bindGroup), passEncoder.draw(6), passEncoder.end(), WebGPUPlayer.device.queue.submit([commandEncoder.finish()]);}}setupRendering() {this.animFrameId = this.frameCallback(this.boundDrawFrame);}forceDrawFrame() {}refreshPlayer() {this.updateCanvas();}destroy() {if (super.destroy(), this.isStopped = !0, this.texture) this.texture.destroy(), this.texture = null;if (this.pipeline = null, this.bindGroup = null, this.sampler = null, this.context) this.context.unconfigure(), this.context = null;console.log("WebGPU context successfully freed.");}} +class WebGPUPlayer extends BaseCanvasPlayer {static device;context;externalTexture;pipeline;sampler;bindGroup;boundDrawFrame;optionsUpdated = !1;paramsBuffer;static async prepare() {if (!navigator.gpu) return;try {let adapter = await navigator.gpu.requestAdapter({powerPreference: "low-power"});WebGPUPlayer.device = await adapter?.requestDevice();} catch (ex) {alert(ex);}}constructor($video) {super("webgpu", $video, "WebGPUPlayer");this.boundDrawFrame = this.drawFrame.bind(this);}setupShaders() {if (this.context = this.$canvas.getContext("webgpu"), !this.context) {alert("Can't initiate context");return;}let format = navigator.gpu.getPreferredCanvasFormat();this.context.configure({device: WebGPUPlayer.device,format,alphaMode: "opaque"});let shaderModule = WebGPUPlayer.device.createShaderModule({ code: clarity_boost_default3 });this.pipeline = WebGPUPlayer.device.createRenderPipeline({layout: "auto",vertex: {module: shaderModule,entryPoint: "vs_main"},fragment: {module: shaderModule,entryPoint: "fs_main",targets: [{ format }]},primitive: { topology: "triangle-list" }}), this.sampler = WebGPUPlayer.device.createSampler({ magFilter: "linear", minFilter: "linear" }), this.updateCanvas();}prepareUniformBuffer(value, classType) {let uniform = new classType(value), uniformBuffer = WebGPUPlayer.device.createBuffer({size: uniform.byteLength,usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST});return WebGPUPlayer.device.queue.writeBuffer(uniformBuffer, 0, uniform), uniformBuffer;}updateCanvas() {if (this.externalTexture = WebGPUPlayer.device.importExternalTexture({ source: this.$video }), !this.optionsUpdated) this.paramsBuffer = this.prepareUniformBuffer([this.toFilterId(this.options.processing),this.options.sharpness,this.options.brightness / 100,this.options.contrast / 100,this.options.saturation / 100], Float32Array), this.optionsUpdated = !0;this.bindGroup = WebGPUPlayer.device.createBindGroup({layout: this.pipeline.getBindGroupLayout(0),entries: [{ binding: 0, resource: this.sampler },{ binding: 1, resource: this.externalTexture },{ binding: 2, resource: { buffer: this.paramsBuffer } }]});}drawFrame() {if (this.isStopped) return;this.animFrameId = this.frameCallback(this.boundDrawFrame);let draw = !0;if (this.targetFps === 0) draw = !1;else if (this.targetFps < 60) {let currentTime = performance.now();if (currentTime - this.lastFrameTime < this.frameInterval) draw = !1;else this.lastFrameTime = currentTime;}if (draw) {this.updateCanvas();let commandEncoder = WebGPUPlayer.device.createCommandEncoder(), passEncoder = commandEncoder.beginRenderPass({colorAttachments: [{view: this.context.getCurrentTexture().createView(),loadOp: "clear",storeOp: "store",clearValue: [0, 0, 0, 1]}]});passEncoder.setPipeline(this.pipeline), passEncoder.setBindGroup(0, this.bindGroup), passEncoder.draw(6), passEncoder.end(), WebGPUPlayer.device.queue.submit([commandEncoder.finish()]);}}setupRendering() {this.animFrameId = this.frameCallback(this.boundDrawFrame);}forceDrawFrame() {}refreshPlayer() {this.optionsUpdated = !1, this.updateCanvas();}destroy() {if (super.destroy(), this.isStopped = !0, this.pipeline = null, this.bindGroup = null, this.sampler = null, this.paramsBuffer?.destroy(), this.paramsBuffer = null, this.externalTexture = null, this.context) this.context.unconfigure(), this.context = null;console.log("WebGPU context successfully freed.");}} class StreamPlayerManager {static instance;static getInstance = () => StreamPlayerManager.instance ?? (StreamPlayerManager.instance = new StreamPlayerManager);$video;videoPlayer;canvasPlayer;playerType = "default";constructor() {}setVideoElement($video) {this.$video = $video, this.videoPlayer = new VideoPlayer($video, "VideoPlayer"), this.videoPlayer.init();}resizePlayer() {let PREF_RATIO = getStreamPref("video.ratio"), $video = this.$video, isNativeTouchGame = STATES.currentStream.titleInfo?.details.hasNativeTouchSupport, targetWidth, targetHeight, targetObjectFit;if (PREF_RATIO.includes(":")) {let tmp = PREF_RATIO.split(":"), videoRatio = parseFloat(tmp[0]) / parseFloat(tmp[1]), width = 0, height = 0, parentRect = $video.parentElement.getBoundingClientRect();if (parentRect.width / parentRect.height > videoRatio) height = parentRect.height, width = height * videoRatio;else width = parentRect.width, height = width / videoRatio;width = Math.ceil(Math.min(parentRect.width, width)), height = Math.ceil(Math.min(parentRect.height, height)), $video.dataset.width = width.toString(), $video.dataset.height = height.toString();let $parent = $video.parentElement, position = getStreamPref("video.position");if ($parent.style.removeProperty("padding-top"), $parent.dataset.position = position, position === "top-half" || position === "bottom-half") {let padding = Math.floor((window.innerHeight - height) / 4);if (padding > 0) {if (position === "bottom-half") padding *= 3;$parent.style.paddingTop = padding + "px";}}targetWidth = `${width}px`, targetHeight = `${height}px`, targetObjectFit = PREF_RATIO === "16:9" ? "contain" : "fill";} else targetWidth = "100%", targetHeight = "100%", targetObjectFit = PREF_RATIO, $video.dataset.width = window.innerWidth.toString(), $video.dataset.height = window.innerHeight.toString();if ($video.style.width = targetWidth, $video.style.height = targetHeight, $video.style.objectFit = targetObjectFit, this.canvasPlayer) {let $canvas = this.canvasPlayer.getCanvas();$canvas.style.width = targetWidth, $canvas.style.height = targetHeight, $canvas.style.objectFit = targetObjectFit, $video.dispatchEvent(new Event("resize"));}if (isNativeTouchGame && this.playerType !== "default") window.BX_EXPOSED.streamSession.updateDimensions();}switchPlayerType(type, refreshPlayer = !1) {if (this.playerType !== type) {let videoClass = BX_FLAGS.DeviceInfo.deviceType === "android-tv" ? "bx-pixel" : "bx-gone";if (this.cleanUpCanvasPlayer(), type === "default") this.$video.classList.remove(videoClass);else {if (type === "webgpu") this.canvasPlayer = new WebGPUPlayer(this.$video);else this.canvasPlayer = new WebGL2Player(this.$video);this.canvasPlayer.init(), this.videoPlayer.clearFilters(), this.$video.classList.add(videoClass);}this.playerType = type;}refreshPlayer && this.refreshPlayer();}updateOptions(options, refreshPlayer = !1) {(this.canvasPlayer || this.videoPlayer).updateOptions(options, refreshPlayer);}getPlayerElement(elementType) {if (typeof elementType === "undefined") elementType = this.playerType === "default" ? "video" : "canvas";if (elementType !== "video") return this.canvasPlayer?.getCanvas();return this.$video;}getCanvasPlayer() {return this.canvasPlayer;}refreshPlayer() {if (this.playerType === "default") this.videoPlayer.refreshPlayer();else ScreenshotManager.getInstance().updateCanvasFilters("none"), this.canvasPlayer?.refreshPlayer();this.resizePlayer();}getVideoPlayerFilterStyle() {throw new Error("Method not implemented.");}cleanUpCanvasPlayer() {this.canvasPlayer?.destroy(), this.canvasPlayer = null;}destroy() {this.cleanUpCanvasPlayer();}} function patchVideoApi() {let PREF_SKIP_SPLASH_VIDEO = getGlobalPref("ui.splashVideo.skip"), showFunc = function() {if (this.style.visibility = "visible", !this.videoWidth) return;let playerOptions = {processing: getStreamPref("video.processing"),sharpness: getStreamPref("video.processing.sharpness"),saturation: getStreamPref("video.saturation"),contrast: getStreamPref("video.contrast"),brightness: getStreamPref("video.brightness")}, streamPlayerManager = StreamPlayerManager.getInstance();streamPlayerManager.setVideoElement(this), streamPlayerManager.updateOptions(playerOptions, !1), streamPlayerManager.switchPlayerType(getStreamPref("video.player.type")), STATES.currentStream.streamPlayerManager = streamPlayerManager, BxEventBus.Stream.emit("state.playing", {$video: this});}, nativePlay = HTMLMediaElement.prototype.play;HTMLMediaElement.prototype.nativePlay = nativePlay, HTMLMediaElement.prototype.play = function() {if (this.className && this.className.startsWith("XboxSplashVideo")) {if (PREF_SKIP_SPLASH_VIDEO) return this.volume = 0, this.style.display = "none", this.dispatchEvent(new Event("ended")), new Promise(() => {});return nativePlay.apply(this);}let $parent = this.parentElement;if (!this.src && $parent.dataset.testid === "media-container") this.addEventListener("loadedmetadata", showFunc, { once: !0 });return nativePlay.apply(this);};} function patchRtcCodecs() {if (getGlobalPref("stream.video.codecProfile") === "default") return;if (typeof RTCRtpTransceiver === "undefined" || !("setCodecPreferences" in RTCRtpTransceiver.prototype)) return !1;}