Feat : handle settings gracefully
This commit is contained in:
@@ -15,7 +15,8 @@ uniform float u_time;
|
||||
|
||||
void main(){
|
||||
vec2 st=(gl_FragCoord.xy+u_offset)/u_resolution;
|
||||
|
||||
|
||||
|
||||
vec2 q=vec2(0.);
|
||||
q.x=fbm(st);
|
||||
q.y=fbm(st+vec2(1.));
|
||||
@@ -31,4 +32,5 @@ void main(){
|
||||
vec3 color=mix(u_color_start,u_color_end,f*.3);
|
||||
|
||||
gl_FragColor=vec4(color,1.);
|
||||
|
||||
}
|
||||
@@ -6,9 +6,9 @@ import resolveLygia from "https://lygia.xyz/resolve.esm.js"
|
||||
declare const resolveLygia: (source: string) => string
|
||||
|
||||
let interval: number
|
||||
let timeout: number
|
||||
|
||||
const shaderOptions = { quality: 6, framerate: 1000 / 60 }
|
||||
const bounds = { left: 0, bottom: 0 }
|
||||
const shaderOptions = { quality: 1, framerate: 1000 / 60 }
|
||||
const screen = { width: 0, height: 0, devicePixelRatio: 0 }
|
||||
|
||||
const _colorStart = [0, 0, 0]
|
||||
@@ -18,19 +18,22 @@ let fragmentShader = resolveLygia(noise);
|
||||
|
||||
let canvas: OffscreenCanvas
|
||||
|
||||
async function main(canvas: OffscreenCanvas, fragmentShader2: string) {
|
||||
let render: (time: number) => void
|
||||
|
||||
let must_resize = false
|
||||
|
||||
async function main(canvas: OffscreenCanvas) {
|
||||
const gl = canvas.getContext("webgl");
|
||||
if (!gl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// setup GLSL program
|
||||
const program = gl.createProgram()
|
||||
if (!program) { return }
|
||||
|
||||
const fragment = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
if (!fragment) { return }
|
||||
gl.shaderSource(fragment, fragmentShader2)
|
||||
gl.shaderSource(fragment, fragmentShader)
|
||||
gl.compileShader(fragment);
|
||||
if (!gl.getShaderParameter(fragment, gl.COMPILE_STATUS)) {
|
||||
const lastError = gl.getShaderInfoLog(fragment);
|
||||
@@ -87,6 +90,12 @@ async function main(canvas: OffscreenCanvas, fragmentShader2: string) {
|
||||
|
||||
const gl2 = gl
|
||||
function render(time: number) {
|
||||
if (must_resize) {
|
||||
canvas.width = screen.width * shaderOptions.quality / 10
|
||||
canvas.height = screen.height * shaderOptions.quality / 10
|
||||
must_resize = false;
|
||||
}
|
||||
|
||||
time *= 0.001; // convert to seconds
|
||||
// Tell WebGL how to convert from clip space to pixels
|
||||
gl2.viewport(0, 0, gl2.canvas.width, gl2.canvas.height);
|
||||
@@ -110,11 +119,19 @@ async function main(canvas: OffscreenCanvas, fragmentShader2: string) {
|
||||
0, // start at the beginning of the buffer
|
||||
);
|
||||
|
||||
const secondRatio = screen.width / screen.height
|
||||
|
||||
const aspectRatio = Math.max(screen.width, screen.height) * screen.devicePixelRatio
|
||||
gl2.uniform2f(offset, bounds.left, -bounds.bottom);
|
||||
if (secondRatio < 1) {
|
||||
const half = canvas.height / 2
|
||||
gl2.uniform2f(resolutionLocation, canvas.width, canvas.height * secondRatio);
|
||||
gl2.uniform2f(offset, 0, -half + ((half) / (1 / secondRatio)));
|
||||
} else {
|
||||
const half = canvas.width / 2
|
||||
gl2.uniform2f(resolutionLocation, canvas.width / secondRatio, canvas.height);
|
||||
gl2.uniform2f(offset, -half + ((half) / secondRatio), 0);
|
||||
}
|
||||
gl2.uniform1i(quality, shaderOptions.quality)
|
||||
gl2.uniform2f(resolutionLocation, aspectRatio, aspectRatio);
|
||||
|
||||
gl2.uniform3f(colorStart, _colorStart[0], _colorStart[1], _colorStart[2]);
|
||||
gl2.uniform3f(colorEnd, _colorEnd[0], _colorEnd[1], _colorEnd[2]);
|
||||
gl2.uniform1f(timeLocation, time);
|
||||
@@ -123,43 +140,71 @@ async function main(canvas: OffscreenCanvas, fragmentShader2: string) {
|
||||
0, // offset
|
||||
6, // num vertices to process
|
||||
);
|
||||
requestAnimationFrame(render)
|
||||
if (timeout) {
|
||||
self.clearTimeout(timeout)
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render)
|
||||
}, 1000 / shaderOptions.framerate)
|
||||
}
|
||||
console.log("a")
|
||||
}
|
||||
|
||||
// self.clearInterval(interval)
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), shaderOptions.framerate)
|
||||
requestAnimationFrame(render)
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
|
||||
self.clearTimeout(timeout)
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render)
|
||||
}, 1000 / shaderOptions.framerate)
|
||||
return render
|
||||
}
|
||||
|
||||
self.addEventListener("message", (e) => {
|
||||
const data = e.data
|
||||
if (data.command == "initShader") {
|
||||
canvas = data.canvas
|
||||
} else if (data.command == "killShader") {
|
||||
console.log("killShader")
|
||||
self.clearInterval(interval)
|
||||
} else {
|
||||
console.log(screen.devicePixelRatio)
|
||||
bounds.bottom = data.bounds.bottom
|
||||
bounds.left = data.bounds.left
|
||||
shaderOptions.quality = data.shaderOptions.quality
|
||||
screen.devicePixelRatio = data.screen.devicePixelRatio
|
||||
screen.width = data.screen.width
|
||||
screen.height = data.screen.height
|
||||
loadShader()
|
||||
} else if (data.command == "killShader") {
|
||||
console.log("killShader")
|
||||
self.clearInterval(interval)
|
||||
self.clearTimeout(timeout)
|
||||
}
|
||||
else if (data.command == "resumeShader") {
|
||||
self.clearInterval(interval)
|
||||
self.clearTimeout(timeout)
|
||||
shaderOptions.framerate = data.shaderOptions.framerate
|
||||
if (shaderOptions.framerate == 65) {
|
||||
shaderOptions.framerate == Infinity
|
||||
}
|
||||
timeout = self.setTimeout(() => {
|
||||
requestAnimationFrame(render)
|
||||
}, 1000 / shaderOptions.framerate)
|
||||
// interval = self.setInterval(() => requestAnimationFrame(render), 1000 / shaderOptions.framerate)
|
||||
} else if (data.command == "recolorShader") {
|
||||
_colorStart[0] = data.colorStart[0]
|
||||
_colorStart[1] = data.colorStart[1]
|
||||
_colorStart[2] = data.colorStart[2]
|
||||
_colorEnd[0] = data.colorEnd[0]
|
||||
_colorEnd[1] = data.colorEnd[1]
|
||||
_colorEnd[2] = data.colorEnd[2]
|
||||
if (data.command == "reloadShader") {
|
||||
shaderOptions.quality = data.shaderOptions.quality
|
||||
shaderOptions.framerate = data.shaderOptions.framerate
|
||||
reloadShader()
|
||||
}
|
||||
} else if (data.command == "resizeShader") {
|
||||
shaderOptions.quality = data.shaderOptions.quality
|
||||
screen.devicePixelRatio = data.screen.devicePixelRatio
|
||||
screen.width = data.screen.width
|
||||
screen.height = data.screen.height
|
||||
must_resize = true
|
||||
}
|
||||
})
|
||||
|
||||
async function reloadShader() {
|
||||
async function loadShader() {
|
||||
if (!canvas) { return }
|
||||
let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality);
|
||||
main(canvas, fragmentShader2);
|
||||
console.log("a")
|
||||
// let fragmentShader2 = fragmentShader.replace("#define FBM_OCTAVES 6", "#define FBM_OCTAVES " + shaderOptions.quality);
|
||||
const temp = await main(canvas);
|
||||
if (temp) {
|
||||
render = temp
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import ShaderWorker from '~/assets/workers/ShaderWorker?worker'
|
||||
const worker = new ShaderWorker()
|
||||
|
||||
const canvas = ref<HTMLCanvasElement | null>(null)
|
||||
const canvas_resize_temp = ref<HTMLCanvasElement | null>(null)
|
||||
|
||||
export interface Props {
|
||||
colorStart?: [number, number, number]
|
||||
@@ -20,80 +21,80 @@ function initShader() {
|
||||
const offscreen = canvas.value?.transferControlToOffscreen()
|
||||
worker.postMessage({
|
||||
command: "initShader",
|
||||
canvas: offscreen
|
||||
canvas: offscreen,
|
||||
screen: { height: window.innerHeight * devicePixelRatio, width: window.innerWidth * devicePixelRatio, devicePixelRatio },
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
}, [offscreen])
|
||||
}
|
||||
function safeReloadShader() {
|
||||
reloadShader()
|
||||
if (settingsStore.shaderFramerate == 0) {
|
||||
killShader()
|
||||
}
|
||||
resizeShader()
|
||||
}
|
||||
|
||||
function reloadShader() {
|
||||
const bounds = canvas.value?.getBoundingClientRect()
|
||||
if (!bounds) { return }
|
||||
function recolorShader() {
|
||||
worker.postMessage({
|
||||
command: 'reloadShader',
|
||||
quality: settingsStore.shaderQuality,
|
||||
framerate: settingsStore.shaderFramerate,
|
||||
bounds: { left: bounds.left, bottom: bounds.bottom },
|
||||
screen: { height: window.screen.height, width: window.screen.width, devicePixelRatio },
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
command: 'recolorShader',
|
||||
colorStart: props.colorStart,
|
||||
colorEnd: props.colorEnd
|
||||
})
|
||||
}
|
||||
|
||||
function resizeShader() {
|
||||
const bounds = canvas.value?.getBoundingClientRect()
|
||||
if (!bounds) { return }
|
||||
worker.postMessage({
|
||||
command: 'changeBounds',
|
||||
bounds: { left: bounds.left, bottom: bounds.bottom },
|
||||
screen: { height: window.screen.height, width: window.screen.width, devicePixelRatio },
|
||||
colorStart: props.colorStart,
|
||||
colorEnd: props.colorEnd
|
||||
command: 'resizeShader',
|
||||
screen: { height: window.innerHeight * devicePixelRatio, width: window.innerWidth * devicePixelRatio, devicePixelRatio },
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
})
|
||||
}
|
||||
|
||||
function endShader() {
|
||||
function killShader() {
|
||||
worker.postMessage({
|
||||
command: 'killShader'
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => [settingsStore.shaderQuality, settingsStore.shaderFramerate], () => {
|
||||
safeReloadShader()
|
||||
function resumeShader() {
|
||||
worker.postMessage({
|
||||
command: 'resumeShader',
|
||||
shaderOptions: { quality: settingsStore.shaderQuality, framerate: settingsStore.shaderFramerate },
|
||||
})
|
||||
}
|
||||
|
||||
watch(() => [settingsStore.shaderFramerate], () => {
|
||||
if (settingsStore.shaderFramerate == 0) {
|
||||
killShader()
|
||||
return
|
||||
}
|
||||
resumeShader()
|
||||
|
||||
})
|
||||
|
||||
watch(() => [settingsStore.shaderQuality], () => {
|
||||
resizeShader()
|
||||
})
|
||||
|
||||
let has_resized = false
|
||||
watch(() => [props.colorEnd, props.colorStart], () => {
|
||||
if (!has_resized) {
|
||||
has_resized = true
|
||||
nextTick(() => {
|
||||
resizeShader()
|
||||
has_resized = false
|
||||
})
|
||||
}
|
||||
recolorShader()
|
||||
})
|
||||
|
||||
onMounted(async () => {
|
||||
nextTick(() => {
|
||||
recolorShader()
|
||||
initShader()
|
||||
window.addEventListener("resize", resizeShader, true)
|
||||
safeReloadShader()
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener("resize", resizeShader)
|
||||
endShader()
|
||||
killShader()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<canvas v-if="settingsStore.shaderQuality != 0" ref="canvas" id="background">test</canvas>
|
||||
<canvas ref="canvas" id="background" :class="{ pixelated: settingsStore.shaderPixelated }" width="480"
|
||||
height="270">test</canvas>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
@@ -102,5 +103,10 @@ onUnmounted(() => {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
image-rendering: smooth;
|
||||
}
|
||||
|
||||
#background.pixelated {
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
</style>
|
||||
@@ -6,6 +6,9 @@ export interface Props {
|
||||
isVisible: boolean;
|
||||
}
|
||||
|
||||
const _devicePixelRatio = computed(() => devicePixelRatio)
|
||||
const _window = computed(() => window)
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
})
|
||||
|
||||
@@ -15,10 +18,6 @@ const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
watch(() => settingsStore.shaderQuality, async () => {
|
||||
console.log(settingsStore.shaderQuality)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -40,27 +39,28 @@ watch(() => settingsStore.shaderQuality, async () => {
|
||||
<span>Shader Quality</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="range" min="0" max="10" v-model="settingsStore.shaderQuality">
|
||||
<input type="range" min="1" max="10" v-model="settingsStore.shaderQuality">
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>
|
||||
<template v-if="settingsStore.shaderQuality == 0">
|
||||
Disabled
|
||||
</template>
|
||||
<template v-if="settingsStore.shaderQuality > 0">
|
||||
{{ settingsStore.shaderQuality }}
|
||||
</template>
|
||||
</span>
|
||||
<span>{{ _window.innerWidth * _devicePixelRatio * settingsStore.shaderQuality / 10 }}x{{ _window.innerHeight *
|
||||
_devicePixelRatio * settingsStore.shaderQuality / 10 }}</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Shader Framerate</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="range" min="10" max="60" step="5" v-model="settingsStore.shaderFramerate"
|
||||
<input type="range" min="0" max="65" step="5" v-model="settingsStore.shaderFramerate"
|
||||
:disabled="settingsStore.shaderQuality == 0">
|
||||
</div>
|
||||
<div class="dialog-footer">
|
||||
<span>{{ settingsStore.shaderFramerate }} fps</span>
|
||||
<span v-if="settingsStore.shaderFramerate == 65">vsync</span>
|
||||
<span v-else>{{ settingsStore.shaderFramerate }} fps</span>
|
||||
</div>
|
||||
<div class="dialog-header">
|
||||
<span>Pixelated</span>
|
||||
</div>
|
||||
<div class="dialog-body">
|
||||
<input type="checkbox" v-model="settingsStore.shaderPixelated">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ export const useSettingsStore = defineStore("settingstore", {
|
||||
cardStyle: CardStyles.HERON,
|
||||
shaderQuality: 0,
|
||||
shaderFramerate: 10,
|
||||
shaderPixelated: false
|
||||
}),
|
||||
actions: {
|
||||
switchCardStyles() {
|
||||
|
||||
Reference in New Issue
Block a user