fix(brush): stop painting after pointer release
This commit is contained in:
@@ -7,11 +7,17 @@ export type ImageTextureRenderer = {
|
||||
dispose(): void;
|
||||
};
|
||||
|
||||
type TextureEntry =
|
||||
type TextureState =
|
||||
| { status: "loading"; image: HTMLImageElement }
|
||||
| { status: "ready"; texture: WebGLTexture }
|
||||
| { status: "error" };
|
||||
|
||||
type TextureEntry = {
|
||||
source: string;
|
||||
current: TextureState;
|
||||
previousTexture?: WebGLTexture;
|
||||
};
|
||||
|
||||
export function createImageTextureRenderer(context: WebGlRendererContext, invalidate: () => void): ImageTextureRenderer {
|
||||
const { gl } = context;
|
||||
const program = createProgram(gl);
|
||||
@@ -30,11 +36,11 @@ export function createImageTextureRenderer(context: WebGlRendererContext, invali
|
||||
|
||||
return {
|
||||
syncAssets(assets) {
|
||||
const activeKeys = new Set(assets.map(textureKey));
|
||||
for (const [key, entry] of textures) {
|
||||
if (!activeKeys.has(key)) {
|
||||
const activeIds = new Set(assets.map((asset) => asset.id));
|
||||
for (const [assetId, entry] of textures) {
|
||||
if (!activeIds.has(assetId)) {
|
||||
disposeEntry(gl, entry);
|
||||
textures.delete(key);
|
||||
textures.delete(assetId);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -43,7 +49,8 @@ export function createImageTextureRenderer(context: WebGlRendererContext, invali
|
||||
if (!drawRect || drawRect.w <= 0 || drawRect.h <= 0) return true;
|
||||
|
||||
const entry = getTextureEntry(context, textures, asset, invalidate, () => disposed);
|
||||
if (entry.status !== "ready") return false;
|
||||
const texture = renderableTexture(entry);
|
||||
if (!texture) return false;
|
||||
|
||||
gl.enable(gl.SCISSOR_TEST);
|
||||
gl.scissor(drawRect.x, context.canvas.height - drawRect.y - drawRect.h, drawRect.w, drawRect.h);
|
||||
@@ -52,7 +59,7 @@ export function createImageTextureRenderer(context: WebGlRendererContext, invali
|
||||
gl.useProgram(program);
|
||||
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, entry.texture);
|
||||
gl.bindTexture(gl.TEXTURE_2D, texture);
|
||||
gl.uniform1i(samplerLocation, 0);
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||
@@ -85,42 +92,71 @@ function getTextureEntry(
|
||||
invalidate: () => void,
|
||||
isDisposed: () => boolean,
|
||||
): TextureEntry {
|
||||
const key = textureKey(asset);
|
||||
const cached = textures.get(key);
|
||||
if (cached) return cached;
|
||||
const cached = textures.get(asset.id);
|
||||
if (cached && cached.source === asset.source) return cached;
|
||||
|
||||
if (cached) {
|
||||
if (cached.current.status === "ready") {
|
||||
if (cached.previousTexture) context.gl.deleteTexture(cached.previousTexture);
|
||||
cached.previousTexture = cached.current.texture;
|
||||
} else {
|
||||
disposeState(context.gl, cached.current);
|
||||
}
|
||||
cached.source = asset.source;
|
||||
cached.current = loadTextureState(context, asset, cached, invalidate, isDisposed);
|
||||
return cached;
|
||||
}
|
||||
|
||||
const entry: TextureEntry = { source: asset.source, current: { status: "error" } };
|
||||
entry.current = loadTextureState(context, asset, entry, invalidate, isDisposed);
|
||||
textures.set(asset.id, entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
function loadTextureState(
|
||||
context: WebGlRendererContext,
|
||||
asset: Asset,
|
||||
entry: TextureEntry,
|
||||
invalidate: () => void,
|
||||
isDisposed: () => boolean,
|
||||
): TextureState {
|
||||
const image = new Image();
|
||||
textures.set(key, { status: "loading", image });
|
||||
const source = asset.source;
|
||||
image.onload = () => {
|
||||
if (isDisposed()) return;
|
||||
if (isDisposed() || entry.source !== source) return;
|
||||
const texture = createTexture(context.gl, image);
|
||||
disposeEntry(context.gl, textures.get(key));
|
||||
textures.set(key, { status: "ready", texture });
|
||||
if (entry.previousTexture) context.gl.deleteTexture(entry.previousTexture);
|
||||
entry.previousTexture = undefined;
|
||||
entry.current = { status: "ready", texture };
|
||||
invalidate();
|
||||
};
|
||||
image.onerror = () => {
|
||||
if (isDisposed()) return;
|
||||
textures.set(key, { status: "error" });
|
||||
if (isDisposed() || entry.source !== source) return;
|
||||
entry.current = { status: "error" };
|
||||
invalidate();
|
||||
};
|
||||
image.src = asset.source;
|
||||
|
||||
image.src = source;
|
||||
return { status: "loading", image };
|
||||
}
|
||||
|
||||
function textureKey(asset: Asset) {
|
||||
return `${asset.id}:${asset.source}`;
|
||||
function renderableTexture(entry: TextureEntry): WebGLTexture | undefined {
|
||||
if (entry.current.status === "ready") return entry.current.texture;
|
||||
return entry.previousTexture;
|
||||
}
|
||||
|
||||
function disposeEntry(gl: WebGL2RenderingContext, entry: TextureEntry | undefined) {
|
||||
if (!entry) return;
|
||||
if (entry.status === "ready") {
|
||||
gl.deleteTexture(entry.texture);
|
||||
function disposeEntry(gl: WebGL2RenderingContext, entry: TextureEntry) {
|
||||
disposeState(gl, entry.current);
|
||||
if (entry.previousTexture) gl.deleteTexture(entry.previousTexture);
|
||||
}
|
||||
|
||||
function disposeState(gl: WebGL2RenderingContext, state: TextureState) {
|
||||
if (state.status === "ready") {
|
||||
gl.deleteTexture(state.texture);
|
||||
return;
|
||||
}
|
||||
if (entry.status === "loading") {
|
||||
entry.image.onload = null;
|
||||
entry.image.onerror = null;
|
||||
if (state.status === "loading") {
|
||||
state.image.onload = null;
|
||||
state.image.onerror = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user