Compare commits
No commits in common. "6c9e055aaeb4f543724b1fdf85c2fdb3c0670fc2" and "65ba33867bf9ad78bd77fcf4fc70d4bc821c5bd1" have entirely different histories.
6c9e055aae
...
65ba33867b
|
@ -4,7 +4,6 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { getProxiedImageUrl } from '../media-proxy.js';
|
import { getProxiedImageUrl } from '../media-proxy.js';
|
||||||
import { initShaderProgram } from '../webgl.js';
|
|
||||||
|
|
||||||
type ParamTypeToPrimitive = {
|
type ParamTypeToPrimitive = {
|
||||||
'number': number;
|
'number': number;
|
||||||
|
@ -61,6 +60,8 @@ function getValue<T extends keyof ParamTypeToPrimitive>(params: Record<string, a
|
||||||
export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, any>>> {
|
export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, any>>> {
|
||||||
private gl: WebGL2RenderingContext;
|
private gl: WebGL2RenderingContext;
|
||||||
private canvas: HTMLCanvasElement | null = null;
|
private canvas: HTMLCanvasElement | null = null;
|
||||||
|
private renderTextureProgram: WebGLProgram;
|
||||||
|
private renderInvertedTextureProgram: WebGLProgram;
|
||||||
private renderWidth: number;
|
private renderWidth: number;
|
||||||
private renderHeight: number;
|
private renderHeight: number;
|
||||||
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
private originalImage: ImageData | ImageBitmap | HTMLImageElement | HTMLCanvasElement;
|
||||||
|
@ -69,7 +70,6 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
private shaderCache: Map<string, WebGLProgram> = new Map();
|
private shaderCache: Map<string, WebGLProgram> = new Map();
|
||||||
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
|
private perLayerResultTextures: Map<string, WebGLTexture> = new Map();
|
||||||
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
|
private perLayerResultFrameBuffers: Map<string, WebGLFramebuffer> = new Map();
|
||||||
private nopProgram: WebGLProgram;
|
|
||||||
private fxs: [...IEX];
|
private fxs: [...IEX];
|
||||||
private paramTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
|
private paramTextures: Map<string, { texture: WebGLTexture; width: number; height: number; }> = new Map();
|
||||||
|
|
||||||
|
@ -114,13 +114,13 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.originalImage.width, this.originalImage.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.originalImage.width, this.originalImage.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, this.originalImage);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
|
||||||
this.nopProgram = initShaderProgram(this.gl, `#version 300 es
|
this.renderTextureProgram = this.initShaderProgram(`#version 300 es
|
||||||
in vec2 position;
|
in vec2 position;
|
||||||
out vec2 in_uv;
|
out vec2 in_uv;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
in_uv = (position + 1.0) / 2.0;
|
in_uv = (position + 1.0) / 2.0;
|
||||||
gl_Position = vec4(position * vec2(1.0, -1.0), 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`, `#version 300 es
|
`, `#version 300 es
|
||||||
precision mediump float;
|
precision mediump float;
|
||||||
|
@ -134,28 +134,82 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
}
|
}
|
||||||
`);
|
`);
|
||||||
|
|
||||||
// レジスタ番号はシェーダープログラムに属しているわけではなく、独立の存在なので、とりあえず nopProgram を使って設定する(その後は効果が持続する)
|
this.renderInvertedTextureProgram = this.initShaderProgram(`#version 300 es
|
||||||
// ref. https://qiita.com/emadurandal/items/5966c8374f03d4de3266
|
in vec2 position;
|
||||||
const positionLocation = gl.getAttribLocation(this.nopProgram, 'position');
|
out vec2 in_uv;
|
||||||
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
|
||||||
gl.enableVertexAttribArray(positionLocation);
|
void main() {
|
||||||
|
in_uv = (position + 1.0) / 2.0;
|
||||||
|
in_uv.y = 1.0 - in_uv.y;
|
||||||
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
`, `#version 300 es
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
in vec2 in_uv;
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
out vec4 out_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
out_color = texture(u_texture, in_uv);
|
||||||
|
}
|
||||||
|
`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture, invert = false) {
|
public loadShader(type: GLenum, source: string): WebGLShader {
|
||||||
|
const gl = this.gl;
|
||||||
|
|
||||||
|
const shader = gl.createShader(type);
|
||||||
|
if (shader == null) {
|
||||||
|
throw new Error('falied to create shader');
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||||
|
console.error(`falied to compile shader: ${gl.getShaderInfoLog(shader)}`);
|
||||||
|
gl.deleteShader(shader);
|
||||||
|
throw new Error(`falied to compile shader: ${gl.getShaderInfoLog(shader)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
public initShaderProgram(vsSource: string, fsSource: string): WebGLProgram {
|
||||||
|
const gl = this.gl;
|
||||||
|
|
||||||
|
const vertexShader = this.loadShader(gl.VERTEX_SHADER, vsSource);
|
||||||
|
const fragmentShader = this.loadShader(gl.FRAGMENT_SHADER, fsSource);
|
||||||
|
|
||||||
|
const shaderProgram = gl.createProgram();
|
||||||
|
|
||||||
|
gl.attachShader(shaderProgram, vertexShader);
|
||||||
|
gl.attachShader(shaderProgram, fragmentShader);
|
||||||
|
gl.linkProgram(shaderProgram);
|
||||||
|
|
||||||
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||||
|
console.error(`failed to init shader: ${gl.getProgramInfoLog(shaderProgram)}`);
|
||||||
|
throw new Error('failed to init shader');
|
||||||
|
}
|
||||||
|
|
||||||
|
return shaderProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
private renderLayer(layer: ImageEffectorLayer, preTexture: WebGLTexture) {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
|
|
||||||
const fx = this.fxs.find(fx => fx.id === layer.fxId);
|
const fx = this.fxs.find(fx => fx.id === layer.fxId);
|
||||||
if (fx == null) return;
|
if (fx == null) return;
|
||||||
|
|
||||||
const cachedShader = this.shaderCache.get(fx.id);
|
const cachedShader = this.shaderCache.get(fx.id);
|
||||||
const shaderProgram = cachedShader ?? initShaderProgram(this.gl, `#version 300 es
|
const shaderProgram = cachedShader ?? this.initShaderProgram(`#version 300 es
|
||||||
in vec2 position;
|
in vec2 position;
|
||||||
uniform bool u_invert;
|
|
||||||
out vec2 in_uv;
|
out vec2 in_uv;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
in_uv = (position + 1.0) / 2.0;
|
in_uv = (position + 1.0) / 2.0;
|
||||||
gl_Position = u_invert ? vec4(position * vec2(1.0, -1.0), 0.0, 1.0) : vec4(position, 0.0, 1.0);
|
gl_Position = vec4(position, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
`, fx.shader);
|
`, fx.shader);
|
||||||
if (cachedShader == null) {
|
if (cachedShader == null) {
|
||||||
|
@ -167,9 +221,6 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution');
|
const in_resolution = gl.getUniformLocation(shaderProgram, 'in_resolution');
|
||||||
gl.uniform2fv(in_resolution, [this.renderWidth, this.renderHeight]);
|
gl.uniform2fv(in_resolution, [this.renderWidth, this.renderHeight]);
|
||||||
|
|
||||||
const u_invert = gl.getUniformLocation(shaderProgram, 'u_invert');
|
|
||||||
gl.uniform1i(u_invert, invert ? 1 : 0);
|
|
||||||
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
const in_texture = gl.getUniformLocation(shaderProgram, 'in_texture');
|
const in_texture = gl.getUniformLocation(shaderProgram, 'in_texture');
|
||||||
|
@ -202,23 +253,27 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
public render() {
|
public render() {
|
||||||
const gl = this.gl;
|
const gl = this.gl;
|
||||||
|
|
||||||
// 入力をそのまま出力
|
{
|
||||||
if (this.layers.length === 0) {
|
|
||||||
gl.activeTexture(gl.TEXTURE0);
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
|
gl.bindTexture(gl.TEXTURE_2D, this.originalImageTexture);
|
||||||
|
|
||||||
gl.useProgram(this.nopProgram);
|
gl.useProgram(this.renderTextureProgram);
|
||||||
gl.uniform1i(gl.getUniformLocation(this.nopProgram, 'u_texture')!, 0);
|
const u_texture = gl.getUniformLocation(this.renderTextureProgram, 'u_texture');
|
||||||
|
gl.uniform1i(u_texture, 0);
|
||||||
|
const u_resolution = gl.getUniformLocation(this.renderTextureProgram, 'u_resolution');
|
||||||
|
gl.uniform2fv(u_resolution, [this.renderWidth, this.renderHeight]);
|
||||||
|
const positionLocation = gl.getAttribLocation(this.renderTextureProgram, 'position');
|
||||||
|
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.enableVertexAttribArray(positionLocation);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
|
||||||
let preTexture = this.originalImageTexture;
|
let preTexture = this.originalImageTexture;
|
||||||
|
|
||||||
for (const layer of this.layers) {
|
for (const layer of this.layers) {
|
||||||
const isLast = layer === this.layers.at(-1);
|
|
||||||
|
|
||||||
const cachedResultTexture = this.perLayerResultTextures.get(layer.id);
|
const cachedResultTexture = this.perLayerResultTextures.get(layer.id);
|
||||||
const resultTexture = cachedResultTexture ?? createTexture(gl);
|
const resultTexture = cachedResultTexture ?? createTexture(gl);
|
||||||
if (cachedResultTexture == null) {
|
if (cachedResultTexture == null) {
|
||||||
|
@ -228,9 +283,6 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.renderWidth, this.renderHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this.renderWidth, this.renderHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
|
||||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||||
|
|
||||||
if (isLast) {
|
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
||||||
} else {
|
|
||||||
const cachedResultFrameBuffer = this.perLayerResultFrameBuffers.get(layer.id);
|
const cachedResultFrameBuffer = this.perLayerResultFrameBuffers.get(layer.id);
|
||||||
const resultFrameBuffer = cachedResultFrameBuffer ?? gl.createFramebuffer()!;
|
const resultFrameBuffer = cachedResultFrameBuffer ?? gl.createFramebuffer()!;
|
||||||
if (cachedResultFrameBuffer == null) {
|
if (cachedResultFrameBuffer == null) {
|
||||||
|
@ -238,12 +290,23 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
}
|
}
|
||||||
gl.bindFramebuffer(gl.FRAMEBUFFER, resultFrameBuffer);
|
gl.bindFramebuffer(gl.FRAMEBUFFER, resultFrameBuffer);
|
||||||
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resultTexture, 0);
|
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, resultTexture, 0);
|
||||||
}
|
|
||||||
|
|
||||||
this.renderLayer(layer, preTexture, isLast);
|
this.renderLayer(layer, preTexture);
|
||||||
|
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
|
||||||
preTexture = resultTexture;
|
preTexture = resultTexture;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------
|
||||||
|
|
||||||
|
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
||||||
|
gl.useProgram(this.renderInvertedTextureProgram);
|
||||||
|
|
||||||
|
gl.activeTexture(gl.TEXTURE0);
|
||||||
|
gl.bindTexture(gl.TEXTURE_2D, preTexture);
|
||||||
|
|
||||||
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setLayers(layers: ImageEffectorLayer[]) {
|
public async setLayers(layers: ImageEffectorLayer[]) {
|
||||||
|
@ -303,8 +366,6 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
* disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意
|
* disposeCanvas = true だとloseContextを呼ぶため、コンストラクタで渡されたcanvasも再利用不可になるので注意
|
||||||
*/
|
*/
|
||||||
public destroy(disposeCanvas = true) {
|
public destroy(disposeCanvas = true) {
|
||||||
this.gl.deleteProgram(this.nopProgram);
|
|
||||||
|
|
||||||
for (const shader of this.shaderCache.values()) {
|
for (const shader of this.shaderCache.values()) {
|
||||||
this.gl.deleteProgram(shader);
|
this.gl.deleteProgram(shader);
|
||||||
}
|
}
|
||||||
|
@ -325,6 +386,8 @@ export class ImageEffector<IEX extends ReadonlyArray<ImageEffectorFx<any, any, a
|
||||||
}
|
}
|
||||||
this.paramTextures.clear();
|
this.paramTextures.clear();
|
||||||
|
|
||||||
|
this.gl.deleteProgram(this.renderTextureProgram);
|
||||||
|
this.gl.deleteProgram(this.renderInvertedTextureProgram);
|
||||||
this.gl.deleteTexture(this.originalImageTexture);
|
this.gl.deleteTexture(this.originalImageTexture);
|
||||||
|
|
||||||
if (disposeCanvas) {
|
if (disposeCanvas) {
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function loadShader(gl: WebGL2RenderingContext, type: GLenum, source: string): WebGLShader {
|
|
||||||
const shader = gl.createShader(type);
|
|
||||||
if (shader == null) {
|
|
||||||
throw new Error('falied to create shader');
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.shaderSource(shader, source);
|
|
||||||
gl.compileShader(shader);
|
|
||||||
|
|
||||||
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
|
||||||
console.error(`falied to compile shader: ${gl.getShaderInfoLog(shader)}`);
|
|
||||||
gl.deleteShader(shader);
|
|
||||||
throw new Error(`falied to compile shader: ${gl.getShaderInfoLog(shader)}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function initShaderProgram(gl: WebGL2RenderingContext, vsSource: string, fsSource: string): WebGLProgram {
|
|
||||||
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
|
|
||||||
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
|
||||||
|
|
||||||
const shaderProgram = gl.createProgram();
|
|
||||||
|
|
||||||
gl.attachShader(shaderProgram, vertexShader);
|
|
||||||
gl.attachShader(shaderProgram, fragmentShader);
|
|
||||||
gl.linkProgram(shaderProgram);
|
|
||||||
|
|
||||||
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
|
||||||
console.error(`failed to init shader: ${gl.getProgramInfoLog(shaderProgram)}`);
|
|
||||||
throw new Error('failed to init shader');
|
|
||||||
}
|
|
||||||
|
|
||||||
return shaderProgram;
|
|
||||||
}
|
|
Loading…
Reference in New Issue