fix: シェーダーの読み込みが完了してからレンダリングを行うように

This commit is contained in:
kakkokari-gtyih 2025-06-03 23:57:40 +09:00
parent afcc37d886
commit e06f37a7d4
3 changed files with 51 additions and 32 deletions

View File

@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.container"> <div :class="$style.container">
<div :class="$style.preview"> <div :class="$style.preview">
<canvas ref="canvasEl" :class="$style.previewCanvas"></canvas> <canvas ref="canvasEl" :class="$style.previewCanvas"></canvas>
<MkLoading v-if="canvasLoading" :class="$style.previewSpinner"/>
<div :class="$style.previewContainer"> <div :class="$style.previewContainer">
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div> <div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
<div class="_acrylic" :class="$style.previewControls"> <div class="_acrylic" :class="$style.previewControls">
@ -74,6 +75,8 @@ const emit = defineEmits<{
const dialog = useTemplateRef('dialog'); const dialog = useTemplateRef('dialog');
const canvasLoading = ref(false);
async function cancel() { async function cancel() {
if (layers.length > 0) { if (layers.length > 0) {
const { canceled } = await os.confirm({ const { canceled } = await os.confirm({
@ -91,7 +94,9 @@ const layers = reactive<ImageEffectorLayer[]>([]);
watch(layers, async () => { watch(layers, async () => {
if (renderer != null) { if (renderer != null) {
renderer.setLayers(layers); canvasLoading.value = true;
await renderer.setLayers(layers);
canvasLoading.value = false;
} }
}, { deep: true }); }, { deep: true });

View File

@ -20,6 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="$style.container"> <div :class="$style.container">
<div :class="$style.preview"> <div :class="$style.preview">
<canvas ref="canvasEl" :class="$style.previewCanvas"></canvas> <canvas ref="canvasEl" :class="$style.previewCanvas"></canvas>
<MkLoading v-if="canvasLoading" :class="$style.previewSpinner"/>
<div :class="$style.previewContainer"> <div :class="$style.previewContainer">
<div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div> <div class="_acrylic" :class="$style.previewTitle">{{ i18n.ts.preview }}</div>
<div v-if="props.image == null" class="_acrylic" :class="$style.previewControls"> <div v-if="props.image == null" class="_acrylic" :class="$style.previewControls">
@ -89,6 +90,8 @@ import { genId } from '@/utility/id.js';
const $i = ensureSignin(); const $i = ensureSignin();
const canvasLoading = ref(true);
function createTextLayer(): WatermarkPreset['layers'][number] { function createTextLayer(): WatermarkPreset['layers'][number] {
return { return {
id: genId(), id: genId(),
@ -232,6 +235,13 @@ let imageBitmap: ImageBitmap | null = null;
async function initRenderer() { async function initRenderer() {
if (canvasEl.value == null) return; if (canvasEl.value == null) return;
canvasLoading.value = true;
await Promise.all([
sampleImage_3_2_loading,
sampleImage_2_3_loading,
]);
if (sampleImageType.value === '3_2') { if (sampleImageType.value === '3_2') {
renderer = new WatermarkRenderer({ renderer = new WatermarkRenderer({
canvas: canvasEl.value, canvas: canvasEl.value,
@ -270,7 +280,9 @@ async function initRenderer() {
await renderer!.setLayers(preset.layers); await renderer!.setLayers(preset.layers);
renderer!.render(); await renderer!.render();
canvasLoading.value = false;
} }
onMounted(async () => { onMounted(async () => {

View File

@ -196,26 +196,16 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
return shaderProgram; return shaderProgram;
} }
private async renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) { private async getOrLoadShaderProgram(fx: ImageEffectorFx): Promise<WebGLProgram> {
const gl = this.gl; let shaderProgram = this.shaderCache.get(fx.id);
if (shaderProgram) return shaderProgram;
const fx = this.fxs.find(fx => fx.id === layer.fxId);
if (fx == null) return;
const cachedShader = this.shaderCache.get(fx.id);
let shaderProgram: WebGLProgram;
if (cachedShader != null) {
shaderProgram = cachedShader;
} else {
let shaderSource: string; let shaderSource: string;
if (typeof fx.shader === 'string') { if (typeof fx.shader === 'string') {
shaderSource = fx.shader; shaderSource = fx.shader;
} else { } else {
shaderSource = await fx.shader(); shaderSource = await fx.shader();
} }
shaderProgram = this.initShaderProgram(`#version 300 es shaderProgram = this.initShaderProgram(`#version 300 es
in vec2 position; in vec2 position;
out vec2 in_uv; out vec2 in_uv;
@ -225,11 +215,16 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
gl_Position = vec4(position, 0.0, 1.0); gl_Position = vec4(position, 0.0, 1.0);
} }
`, shaderSource); `, shaderSource);
this.shaderCache.set(fx.id, shaderProgram);
return shaderProgram;
} }
if (cachedShader == null) { private async renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) {
this.shaderCache.set(fx.id, shaderProgram); const gl = this.gl;
} const fx = this.fxs.find(fx => fx.id === layer.fxId);
if (fx == null) return;
const shaderProgram = await this.getOrLoadShaderProgram(fx);
gl.useProgram(shaderProgram); gl.useProgram(shaderProgram);
@ -268,6 +263,13 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
public async render() { public async render() {
const gl = this.gl; const gl = this.gl;
// 全レイヤーのshaderロードを事前に完了させる
await Promise.all(this.layers.map(async (layer) => {
const fx = this.fxs.find(fx => fx.id === layer.fxId);
if (!fx) return;
await this.getOrLoadShaderProgram(fx);
}));
{ {
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture); gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
@ -359,7 +361,7 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
this.paramTextures.delete(k); this.paramTextures.delete(k);
} }
this.render(); await this.render();
} }
public changeResolution(width: number, height: number) { public changeResolution(width: number, height: number) {