(display_object: D, rotation: nu
radianY = radianY + radian - radianX;
radianX = radian;
- matrix.b = scaleX * Math.sin(radianX);
- if (matrix.b === 1 || matrix.b === -1) {
- matrix.a = 0;
- } else {
- matrix.a = scaleX * Math.cos(radianX);
- }
+ // cosが0(sinが±1)の時のみ a/d を 0 にクリアする。
+ // 以前は matrix.b の値そのものを見ていたため、scaleX×sin(θ) がたまたま
+ // ±1 になるケース(例: scaleX=2, rotation=30°)で誤検出が発生し
+ // matrix.a が消えて幅が潰れる不具合を起こしていた。
+ const sinX = Math.sin(radianX);
+ const cosX = Math.cos(radianX);
+ matrix.b = scaleX * sinX;
+ matrix.a = Math.abs(sinX) === 1 ? 0 : scaleX * cosX;
- matrix.c = -scaleY * Math.sin(radianY);
- if (matrix.c === 1 || matrix.c === -1) {
- matrix.d = 0;
- } else {
- matrix.d = scaleY * Math.cos(radianY);
- }
+ const sinY = Math.sin(radianY);
+ const cosY = Math.cos(radianY);
+ matrix.c = -scaleY * sinY;
+ matrix.d = Math.abs(sinY) === 1 ? 0 : scaleY * cosY;
}
display_object.$scaleX = null;
diff --git a/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGenerateRenderQueueUseCase.ts b/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGenerateRenderQueueUseCase.ts
index 396045ab..67418821 100644
--- a/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGenerateRenderQueueUseCase.ts
+++ b/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGenerateRenderQueueUseCase.ts
@@ -13,6 +13,7 @@ import { execute as displayObjectIsMaskReflectedInDisplayUseCase } from "../../D
import { execute as displayObjectContainerGenerateClipQueueUseCase } from "../../DisplayObjectContainer/usecase/DisplayObjectContainerGenerateClipQueueUseCase";
import { execute as displayObjectBlendToNumberService } from "../../DisplayObject/service/DisplayObjectBlendToNumberService";
import { execute as displayObjectContainerGetLayerBoundsUseCase } from "./DisplayObjectContainerGetLayerBoundsUseCase";
+import { execute as displayObjectContainerGetLayerScaleUseCase } from "./DisplayObjectContainerGetLayerScaleUseCase";
import { execute as displayObjectContainerCalcBoundsMatrixUseCase } from "../../DisplayObjectContainer/usecase/DisplayObjectContainerCalcBoundsMatrixUseCase";
import { stage } from "../../Stage";
import { renderQueue } from "@next2d/render-queue";
@@ -51,7 +52,8 @@ export const execute = (
matrix: Float32Array,
color_transform: Float32Array,
renderer_width: number,
- renderer_height: number
+ renderer_height: number,
+ in_filter_layer: boolean = false
): void => {
if (!display_object_container.visible) {
@@ -120,6 +122,9 @@ export const execute =
(
// キャッシュテクスチャは親のスケールを含むサイズで描画し、
// コンポジットは setTransform(1,0,0,1,x,y) で1:1描画されるため正しい画面サイズになる
// 親の移動はキャッシュヒット(位置だけ更新)、スケール変更はキャッシュミス(再描画)
+ // 親がfilter等の中間レイヤー内にいる場合、filter layer 側で layerScale倍の
+ // 解像度を確保してから最終compose時に 1/layerScale で縮小合成する仕組みによって
+ // cacheAsBitmap子のテクスチャが親layerに収まる (issue #274 根本対応)。
const cacheMatrix = display_object_container.cacheAsBitmap;
if (cacheMatrix) {
@@ -184,15 +189,19 @@ export const execute =
(
const screenX = matrix[0] * localOriginX + matrix[2] * localOriginY + matrix[4];
const screenY = matrix[1] * localOriginX + matrix[3] * localOriginY + matrix[5];
- // Shapeと同様にmatrixにcacheScaleを乗算して送る
- // レンダラーで matrix/renderScale → cacheScale成分が反映される
+ // 通常: matrixにcacheScaleを乗算。レンダラで matrix/renderScale → cacheScale成分が反映される。
+ // issue #274: 親がfilter layer を持ち、layer を cacheScale 倍解像度で確保している場合は、
+ // 親のtMatrix に既に layerScale (=cacheScale) が掛かっているため、cacheScale 乗算を省略して
+ // 合成 scale=1 で layer にちょうど収まるようにする。
+ const hitMatrixScaleX = in_filter_layer ? 1 : cacheScaleX;
+ const hitMatrixScaleY = in_filter_layer ? 1 : cacheScaleY;
renderQueue.push(1,
0, 0, // layerWidth/Height: HIT時は未使用
2, 1, display_object_container.instanceId, bitmapCacheKey,
cacheFilterBounds[0], cacheFilterBounds[1], cacheFilterBounds[2], cacheFilterBounds[3],
renderScaleX, renderScaleY,
- matrix[0] * cacheScaleX, matrix[1] * cacheScaleX,
- matrix[2] * cacheScaleY, matrix[3] * cacheScaleY,
+ matrix[0] * hitMatrixScaleX, matrix[1] * hitMatrixScaleX,
+ matrix[2] * hitMatrixScaleY, matrix[3] * hitMatrixScaleY,
screenX, screenY,
tColorTransform[0], tColorTransform[1], tColorTransform[2], tColorTransform[3],
tColorTransform[4], tColorTransform[5], tColorTransform[6], tColorTransform[7]
@@ -249,14 +258,17 @@ export const execute =
(
const missScreenX = matrix[0] * localOriginX + matrix[2] * localOriginY + matrix[4];
const missScreenY = matrix[1] * localOriginX + matrix[3] * localOriginY + matrix[5];
+ // HITパスと同じ規則: filter layer 内では cacheScale 乗算を省略
+ const missMatrixScaleX = in_filter_layer ? 1 : cacheScaleX;
+ const missMatrixScaleY = in_filter_layer ? 1 : cacheScaleY;
renderQueue.push(
1,
localLayerWidth, localLayerHeight,
2, 0, display_object_container.instanceId, bitmapCacheKey,
cacheFilterBounds[0], cacheFilterBounds[1], cacheFilterBounds[2], cacheFilterBounds[3],
renderScaleX, renderScaleY,
- matrix[0] * cacheScaleX, matrix[1] * cacheScaleX,
- matrix[2] * cacheScaleY, matrix[3] * cacheScaleY,
+ matrix[0] * missMatrixScaleX, matrix[1] * missMatrixScaleX,
+ matrix[2] * missMatrixScaleY, matrix[3] * missMatrixScaleY,
missScreenX, missScreenY,
tColorTransform[0], tColorTransform[1], tColorTransform[2], tColorTransform[3],
tColorTransform[4], tColorTransform[5], tColorTransform[6], tColorTransform[7],
@@ -368,16 +380,29 @@ export const execute =
(
display_object_container, matrix
);
+ // 子孫にcacheAsBitmapを持つ要素があれば、そのスケールに合わせて
+ // filterレイヤーの解像度を引き上げる。これにより親レイヤー内で
+ // 子のcacheScale倍テクスチャが端まで収まる (issue #274 発展対応)。
+ const layerScale = new Float32Array([1, 1]);
+ displayObjectContainerGetLayerScaleUseCase(
+ display_object_container, layerScale
+ );
+ const layerScaleX = layerScale[0];
+ const layerScaleY = layerScale[1];
+
+ const layerWidth = Math.ceil(Math.abs(layerBounds[2] - layerBounds[0]) * layerScaleX);
+ const layerHeight = Math.ceil(Math.abs(layerBounds[3] - layerBounds[1]) * layerScaleY);
+
if (filterCache) {
// キャッシュがあって、変更がなければキャッシュを使用
if (!updated) {
renderQueue.push(1,
- Math.ceil(Math.abs(layerBounds[2] - layerBounds[0])),
- Math.ceil(Math.abs(layerBounds[3] - layerBounds[1])),
+ layerWidth, layerHeight,
1, 1, display_object_container.instanceId, filterKey,
bounds[0], bounds[1], bounds[2], bounds[3],
tMatrix[0], tMatrix[1], tMatrix[2], tMatrix[3], layerBounds[0], layerBounds[1],
+ layerScaleX, layerScaleY,
tColorTransform[0], tColorTransform[1], tColorTransform[2], tColorTransform[3],
tColorTransform[4], tColorTransform[5], tColorTransform[6], tColorTransform[7]
);
@@ -400,23 +425,26 @@ export const execute =
(
renderQueue.push(
1,
- Math.ceil(Math.abs(layerBounds[2] - layerBounds[0])),
- Math.ceil(Math.abs(layerBounds[3] - layerBounds[1])),
+ layerWidth, layerHeight,
1, 0, display_object_container.instanceId, filterKey,
bounds[0], bounds[1], bounds[2], bounds[3],
tMatrix[0], tMatrix[1], tMatrix[2], tMatrix[3], layerBounds[0], layerBounds[1],
+ layerScaleX, layerScaleY,
tColorTransform[0], tColorTransform[1], tColorTransform[2], tColorTransform[3],
tColorTransform[4], tColorTransform[5], tColorTransform[6], tColorTransform[7],
params.length
);
renderQueue.set(new Float32Array(params));
- const fa0 = tMatrix[0];
- const fa1 = tMatrix[1];
- const fa2 = tMatrix[2];
- const fa3 = tMatrix[3];
- const faTx = tMatrix[4] - layerBounds[0];
- const faTy = tMatrix[5] - layerBounds[1];
+ // 子に渡すtMatrixは layerScale 倍で scale して、layer 内の
+ // 拡張座標系で描画させる。layer自体は compose時に 1/layerScaleで
+ // 縮小合成され最終的に元サイズで表示される。
+ const fa0 = tMatrix[0] * layerScaleX;
+ const fa1 = tMatrix[1] * layerScaleX;
+ const fa2 = tMatrix[2] * layerScaleY;
+ const fa3 = tMatrix[3] * layerScaleY;
+ const faTx = (tMatrix[4] - layerBounds[0]) * layerScaleX;
+ const faTy = (tMatrix[5] - layerBounds[1]) * layerScaleY;
if (tMatrix !== matrix) {
Matrix.release(tMatrix);
@@ -642,6 +670,11 @@ export const execute =
(
continue;
}
+ // 自身が filter を発動した、または既に filter layer 内にいる場合は、
+ // 子コンテナに伝播して cacheAsBitmap 合成scaleの二重適用を防ぐ
+ const childInFilterLayer = in_filter_layer
+ || Boolean(display_object_container.filters && display_object_container.filters.length);
+
switch (true) {
case child.isContainerEnabled: // 0x00
@@ -651,7 +684,8 @@ export const execute =
(
tMatrix,
tColorTransform,
renderer_width,
- renderer_height
+ renderer_height,
+ childInFilterLayer
);
break;
diff --git a/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGetLayerScaleUseCase.test.ts b/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGetLayerScaleUseCase.test.ts
new file mode 100644
index 00000000..b944338b
--- /dev/null
+++ b/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGetLayerScaleUseCase.test.ts
@@ -0,0 +1,137 @@
+import { execute } from "./DisplayObjectContainerGetLayerScaleUseCase";
+import { Shape } from "../../Shape";
+import { Sprite } from "../../Sprite";
+import { DisplayObjectContainer } from "../../DisplayObjectContainer";
+import { Matrix } from "@next2d/geom";
+import { describe, expect, it } from "vitest";
+
+describe("DisplayObjectContainerGetLayerScaleUseCase.ts test", () =>
+{
+ it("子要素がない場合はoutが初期値のまま", () =>
+ {
+ const container = new DisplayObjectContainer();
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBe(1);
+ expect(out[1]).toBe(1);
+ });
+
+ it("cacheAsBitmap を持たない子コンテナはoutを変更しない", () =>
+ {
+ const container = new DisplayObjectContainer();
+ const child = new Sprite();
+ container.addChild(child);
+
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBe(1);
+ expect(out[1]).toBe(1);
+ });
+
+ it("cacheAsBitmap を持つ子コンテナのスケールを反映する", () =>
+ {
+ const container = new DisplayObjectContainer();
+ const child = new Sprite();
+ child.cacheAsBitmap = new Matrix(1.5, 0, 0, 2.0, 0, 0);
+ container.addChild(child);
+
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBeCloseTo(1.5, 5);
+ expect(out[1]).toBeCloseTo(2.0, 5);
+ });
+
+ it("複数子の cacheAsBitmap のうち最大値を採用する", () =>
+ {
+ const container = new DisplayObjectContainer();
+
+ const childA = new Sprite();
+ childA.cacheAsBitmap = new Matrix(1.2, 0, 0, 3.0, 0, 0);
+ container.addChild(childA);
+
+ const childB = new Sprite();
+ childB.cacheAsBitmap = new Matrix(2.5, 0, 0, 1.5, 0, 0);
+ container.addChild(childB);
+
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBeCloseTo(2.5, 5);
+ expect(out[1]).toBeCloseTo(3.0, 5);
+ });
+
+ it("孫のcacheAsBitmapも再帰的に収集する", () =>
+ {
+ const root = new DisplayObjectContainer();
+ const middle = new Sprite();
+ root.addChild(middle);
+
+ const leaf = new Sprite();
+ leaf.cacheAsBitmap = new Matrix(1.5, 0, 0, 1.5, 0, 0);
+ middle.addChild(leaf);
+
+ const out = new Float32Array([1, 1]);
+ execute(root, out);
+ expect(out[0]).toBeCloseTo(1.5, 5);
+ expect(out[1]).toBeCloseTo(1.5, 5);
+ });
+
+ it("既存のoutの値が大きい場合は上書きしない (max を取る)", () =>
+ {
+ const container = new DisplayObjectContainer();
+ const child = new Sprite();
+ child.cacheAsBitmap = new Matrix(1.2, 0, 0, 1.2, 0, 0);
+ container.addChild(child);
+
+ const out = new Float32Array([2.0, 2.0]);
+ execute(container, out);
+ expect(out[0]).toBeCloseTo(2.0, 5);
+ expect(out[1]).toBeCloseTo(2.0, 5);
+ });
+
+ it("非表示 (visible=false) の子はスキップする", () =>
+ {
+ const container = new DisplayObjectContainer();
+
+ const hidden = new Sprite();
+ hidden.cacheAsBitmap = new Matrix(3.0, 0, 0, 3.0, 0, 0);
+ hidden.visible = false;
+ container.addChild(hidden);
+
+ const visibleChild = new Sprite();
+ visibleChild.cacheAsBitmap = new Matrix(1.5, 0, 0, 1.5, 0, 0);
+ container.addChild(visibleChild);
+
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBeCloseTo(1.5, 5);
+ expect(out[1]).toBeCloseTo(1.5, 5);
+ });
+
+ it("非Container (Shape) の子はスキップする", () =>
+ {
+ const container = new DisplayObjectContainer();
+ const shape = new Shape();
+ container.addChild(shape);
+
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBe(1);
+ expect(out[1]).toBe(1);
+ });
+
+ it("cacheAsBitmap に回転成分がある場合も scale 成分を抽出する", () =>
+ {
+ const container = new DisplayObjectContainer();
+ const child = new Sprite();
+ // 45度回転 + scale 2 相当: a=cos*2, b=sin*2 ... sqrt(a^2+b^2)=2
+ const cos = Math.cos(Math.PI / 4);
+ const sin = Math.sin(Math.PI / 4);
+ child.cacheAsBitmap = new Matrix(cos * 2, sin * 2, -sin * 2, cos * 2, 0, 0);
+ container.addChild(child);
+
+ const out = new Float32Array([1, 1]);
+ execute(container, out);
+ expect(out[0]).toBeCloseTo(2, 5);
+ expect(out[1]).toBeCloseTo(2, 5);
+ });
+});
diff --git a/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGetLayerScaleUseCase.ts b/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGetLayerScaleUseCase.ts
new file mode 100644
index 00000000..766f845d
--- /dev/null
+++ b/packages/display/src/DisplayObjectContainer/usecase/DisplayObjectContainerGetLayerScaleUseCase.ts
@@ -0,0 +1,49 @@
+import type { DisplayObject } from "../../DisplayObject";
+import type { DisplayObjectContainer } from "../../DisplayObjectContainer";
+
+/**
+ * @description コンテナ内部の子孫が描画時に必要とする最大cacheAsBitmapスケールを返却します。
+ * 親がfilter/cacheAsBitmap等のレイヤーを作成する際、そのテクスチャの解像度を
+ * 子孫のcacheScaleに合わせて拡張しないと、描画時にテクスチャ不足で端が欠ける。
+ * 戻り値は [scaleX, scaleY] の2要素で、いずれも1以上。
+ * Return the maximum cacheAsBitmap scale required by descendants.
+ * The parent layer texture must reserve this much resolution to avoid
+ * cropping descendants that render at cacheScale resolution.
+ *
+ * @param {DisplayObjectContainer} display_object_container
+ * @param {Float32Array} out [scaleX, scaleY] を格納するバッファ
+ * @return {void}
+ * @method
+ * @protected
+ */
+export const execute =
(
+ display_object_container: P,
+ out: Float32Array
+): void => {
+ const children = display_object_container.children;
+ for (let idx = 0; idx < children.length; idx++) {
+
+ const child = children[idx] as DisplayObject;
+ if (!child || !child.visible) {
+ continue;
+ }
+
+ if (child.isContainerEnabled) {
+ const container = child as DisplayObjectContainer;
+ const cache = container.cacheAsBitmap;
+ if (cache) {
+ const m = cache.rawData;
+ const csx = Math.sqrt(m[0] * m[0] + m[1] * m[1]);
+ const csy = Math.sqrt(m[2] * m[2] + m[3] * m[3]);
+ if (csx > out[0]) {
+ out[0] = csx;
+ }
+ if (csy > out[1]) {
+ out[1] = csy;
+ }
+ }
+ // 孫以下にも cacheAsBitmap がある可能性があるため再帰
+ execute(container, out);
+ }
+ }
+};
diff --git a/packages/display/src/Graphics/service/GraphicsToNumberArrayService.ts b/packages/display/src/Graphics/service/GraphicsToNumberArrayService.ts
index 2dd35dda..3dcc694e 100644
--- a/packages/display/src/Graphics/service/GraphicsToNumberArrayService.ts
+++ b/packages/display/src/Graphics/service/GraphicsToNumberArrayService.ts
@@ -123,7 +123,7 @@ export const execute = (recodes : any[] | null): any[] =>
{
const type: IGradientType = recodes[idx++];
const stops: IColorStop[] = recodes[idx++];
- const matrix: Float32Array = recodes[idx++];
+ const matrix: ArrayLike = recodes[idx++];
const spread: ISpreadMethod = recodes[idx++];
const interpolation: IInterpolationMethod = recodes[idx++];
const focal: number = recodes[idx++];
@@ -146,7 +146,12 @@ export const execute = (recodes : any[] | null): any[] =>
);
}
- array.push(...matrix);
+ // JSON由来のデシリアライズではFloat32Arrayがkeyed object
+ // ({0:v,...,5:v})になるためspreadではなくindexアクセスで読む
+ array.push(
+ matrix[0], matrix[1], matrix[2],
+ matrix[3], matrix[4], matrix[5]
+ );
switch (spread) {
@@ -225,7 +230,7 @@ export const execute = (recodes : any[] | null): any[] =>
const type: IGradientType = recodes[idx++];
const stops: IColorStop[] = recodes[idx++];
- const matrix: Float32Array = recodes[idx++];
+ const matrix: ArrayLike = recodes[idx++];
const spread: ISpreadMethod = recodes[idx++];
const interpolation: IInterpolationMethod = recodes[idx++];
const focal: number = recodes[idx++];
@@ -244,7 +249,12 @@ export const execute = (recodes : any[] | null): any[] =>
);
}
- array.push(...matrix);
+ // JSON由来のデシリアライズではFloat32Arrayがkeyed object
+ // ({0:v,...,5:v})になるためspreadではなくindexアクセスで読む
+ array.push(
+ matrix[0], matrix[1], matrix[2],
+ matrix[3], matrix[4], matrix[5]
+ );
switch (spread) {
diff --git a/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.test.ts b/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.test.ts
index 5b75a1e9..4328ad4f 100644
--- a/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.test.ts
+++ b/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.test.ts
@@ -137,6 +137,8 @@ describe("DisplayObjectContainerRenderUseCase.js test", () => {
data.push(-10, -10, 110, 110);
// matrix (6)
data.push(1, 0, 0, 1, 0, 0);
+ // layerScaleX, layerScaleY (2)
+ data.push(1, 1);
// colorTransform (8)
data.push(1, 1, 1, 1, 0, 0, 0, 0);
diff --git a/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.ts b/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.ts
index fa08b0c9..d314f72b 100644
--- a/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.ts
+++ b/packages/renderer/src/DisplayObjectContainer/usecase/DisplayObjectContainerRenderUseCase.ts
@@ -46,6 +46,9 @@ export const execute = (
let filterParams: Float32Array | null = null;
let matrix: Float32Array | null = null;
let colorTransform: Float32Array | null = null;
+ // filter layer内部スケール (子孫のcacheAsBitmap最大値)。compose時に 1/layerScaleで縮小合成する。
+ let layerScaleX = 1;
+ let layerScaleY = 1;
if (useLayer) {
layerWidth = render_queue[index++];
@@ -113,6 +116,9 @@ export const execute = (
matrix = render_queue.subarray(index, index + 6);
index += 6;
+ // layerScale (HIT時は合成時のscale補正には未使用)
+ index += 2;
+
colorTransform = render_queue.subarray(index, index + 8);
index += 8;
@@ -130,6 +136,9 @@ export const execute = (
matrix = render_queue.subarray(index, index + 6);
index += 6;
+ layerScaleX = render_queue[index++];
+ layerScaleY = render_queue[index++];
+
colorTransform = render_queue.subarray(index, index + 8);
index += 8;
@@ -331,7 +340,8 @@ export const execute = (
$context.containerEndLayer(
blendMode, matrix!, colorTransform,
useFilter, filterBounds, filterParams,
- uniqueKey, filterKey
+ uniqueKey, filterKey,
+ layerScaleX, layerScaleY
);
}
diff --git a/packages/webgl/src/AtlasManager.ts b/packages/webgl/src/AtlasManager.ts
index 02b9ae96..d15eddfa 100644
--- a/packages/webgl/src/AtlasManager.ts
+++ b/packages/webgl/src/AtlasManager.ts
@@ -29,17 +29,19 @@ export const $setAtlasAttachmentObject = (attachment_object: IAttachmentObject):
export const $getAtlasAttachmentObject = (): IAttachmentObject =>
{
- if (!($activeAtlasIndex in $atlasAttachmentObjects)) {
- $setAtlasAttachmentObject(
- frameBufferManagerGetAttachmentObjectUseCase($RENDER_MAX_SIZE, $RENDER_MAX_SIZE, true)
+ let attachmentObject = $atlasAttachmentObjects[$activeAtlasIndex];
+ if (!attachmentObject) {
+ attachmentObject = frameBufferManagerGetAttachmentObjectUseCase(
+ $RENDER_MAX_SIZE, $RENDER_MAX_SIZE, true
);
+ $atlasAttachmentObjects[$activeAtlasIndex] = attachmentObject;
}
- return $atlasAttachmentObjects[$activeAtlasIndex];
+ return attachmentObject;
};
export const $hasAtlasAttachmentObject = (): boolean =>
{
- return $activeAtlasIndex in $atlasAttachmentObjects;
+ return !!$atlasAttachmentObjects[$activeAtlasIndex];
};
export const $rootNodes: TexturePacker[] = [];
@@ -58,15 +60,17 @@ const $transferBounds: Float32Array[] = [];
export const $getActiveTransferBounds = (index: number): Float32Array =>
{
- if (!(index in $transferBounds)) {
- $transferBounds[index] = new Float32Array([
+ let bounds = $transferBounds[index];
+ if (!bounds) {
+ bounds = new Float32Array([
$MAX_VALUE,
$MAX_VALUE,
$MIN_VALUE,
$MIN_VALUE
]);
+ $transferBounds[index] = bounds;
}
- return $transferBounds[index];
+ return bounds;
};
export const $clearTransferBounds = (): void =>
diff --git a/packages/webgl/src/Context.ts b/packages/webgl/src/Context.ts
index f53b295d..31b68a61 100644
--- a/packages/webgl/src/Context.ts
+++ b/packages/webgl/src/Context.ts
@@ -502,12 +502,15 @@ export class Context
filter_bounds: Float32Array | null,
filter_params: Float32Array | null,
unique_key: string,
- filter_key: string
+ filter_key: string,
+ layer_scale_x: number = 1,
+ layer_scale_y: number = 1
): void {
contextContainerEndLayerUseCase(
blend_mode, matrix, color_transform,
use_filter, filter_bounds, filter_params,
- unique_key, filter_key
+ unique_key, filter_key,
+ layer_scale_x, layer_scale_y
);
}
diff --git a/packages/webgl/src/Context/usecase/ContextContainerEndLayerUseCase.ts b/packages/webgl/src/Context/usecase/ContextContainerEndLayerUseCase.ts
index e215fcdf..f5888bad 100644
--- a/packages/webgl/src/Context/usecase/ContextContainerEndLayerUseCase.ts
+++ b/packages/webgl/src/Context/usecase/ContextContainerEndLayerUseCase.ts
@@ -55,7 +55,11 @@ export const execute = (
filter_bounds: Float32Array | null,
filter_params: Float32Array | null,
unique_key: string,
- filter_key: string
+ filter_key: string,
+ // layer_scale は display 側で layer サイズに反映済み。
+ // compose時は layer 全体を等倍でmainに貼るので直接は参照しない。
+ _layer_scale_x: number = 1,
+ _layer_scale_y: number = 1
): void => {
// 一時アタッチメントへの描画をフラッシュ
@@ -228,6 +232,11 @@ export const execute = (
}
}
+ // issue #274: layer は layerScale 倍解像度で確保されており、
+ // 子孫のcacheAsBitmap子は既にその倍解像度でlayer内に収まっている。
+ // cacheAsBitmap の仕様としてスクリーン上も cacheScale 倍のサイズで
+ // 表示されるため、ここでの縮小は行わずそのまま main に合成する。
+
// キャッシュに保存
if (unique_key) {
$cacheStore.set(unique_key, "fKey", filter_key);
@@ -268,6 +277,7 @@ export const execute = (
$context.bind($context.$mainAttachmentObject as IAttachmentObject);
if (textureObject) {
+ // cacheAsBitmap と同様、layer は layerScale 倍解像度のままmainに合成する
$context.reset();
$context.globalCompositeOperation = blend_mode;
blendDrawFilterToMainUseCase(
diff --git a/packages/webgl/src/Mesh.ts b/packages/webgl/src/Mesh.ts
index 07629b0f..67bb0d53 100644
--- a/packages/webgl/src/Mesh.ts
+++ b/packages/webgl/src/Mesh.ts
@@ -73,7 +73,9 @@ export const $addFillBuffer = (buffer: Float32Array): void =>
const length = buffer.length + $fillBufferOffset;
if (length > $fillBuffer.length) {
const newBuffer = new Float32Array($upperPowerOfTwo(length));
- newBuffer.set($fillBuffer);
+ if ($fillBufferOffset > 0) {
+ newBuffer.set($fillBuffer.subarray(0, $fillBufferOffset));
+ }
newBuffer.set(buffer, $fillBufferOffset);
$fillBuffer = newBuffer;
diff --git a/packages/webgl/src/TextureManager/service/TextureManagerBindService.ts b/packages/webgl/src/TextureManager/service/TextureManagerBindService.ts
index 2c449121..8271d429 100644
--- a/packages/webgl/src/TextureManager/service/TextureManagerBindService.ts
+++ b/packages/webgl/src/TextureManager/service/TextureManagerBindService.ts
@@ -25,15 +25,19 @@ export const execute = (
smooth: boolean = false
): void => {
- if ($activeTextureUnit === -1 || unit !== $activeTextureUnit) {
+ if ($activeTextureUnit !== unit) {
$setActiveTextureUnit(unit);
$gl.activeTexture(unit);
}
- const boundTextures = $boundTextures[index];
- if (boundTextures !== null && texture_object !== null
- && boundTextures.id === texture_object.id
- || texture_object === boundTextures
+ const boundTexture = $boundTextures[index];
+ if (boundTexture === texture_object) {
+ // Same reference, already bound to this unit.
+ return;
+ }
+ if (boundTexture !== null
+ && texture_object !== null
+ && boundTexture.id === texture_object.id
) {
return;
}
@@ -43,7 +47,8 @@ export const execute = (
if (texture_object && texture_object.smooth !== smooth) {
texture_object.smooth = smooth;
- $gl.texParameteri($gl.TEXTURE_2D, $gl.TEXTURE_MIN_FILTER, smooth ? $gl.LINEAR : $gl.NEAREST);
- $gl.texParameteri($gl.TEXTURE_2D, $gl.TEXTURE_MAG_FILTER, smooth ? $gl.LINEAR : $gl.NEAREST);
+ const filter = smooth ? $gl.LINEAR : $gl.NEAREST;
+ $gl.texParameteri($gl.TEXTURE_2D, $gl.TEXTURE_MIN_FILTER, filter);
+ $gl.texParameteri($gl.TEXTURE_2D, $gl.TEXTURE_MAG_FILTER, filter);
}
};
\ No newline at end of file
diff --git a/packages/webgl/src/TextureManager/usecase/TextureManagerReleaseTextureObjectUseCase.ts b/packages/webgl/src/TextureManager/usecase/TextureManagerReleaseTextureObjectUseCase.ts
index f3ec47e7..adc33407 100644
--- a/packages/webgl/src/TextureManager/usecase/TextureManagerReleaseTextureObjectUseCase.ts
+++ b/packages/webgl/src/TextureManager/usecase/TextureManagerReleaseTextureObjectUseCase.ts
@@ -1,5 +1,6 @@
import type { ITextureObject } from "../../interface/ITextureObject";
import { $gl } from "../../WebGLUtil";
+import { $boundTextures } from "../../TextureManager";
/**
* @description TextureObjectをオブジェクトプールに保管、サイズオーバー時は削除します。
@@ -12,5 +13,10 @@ import { $gl } from "../../WebGLUtil";
*/
export const execute = (texture_object: ITextureObject): void =>
{
+ for (let idx = 0; idx < $boundTextures.length; ++idx) {
+ if ($boundTextures[idx] === texture_object) {
+ $boundTextures[idx] = null;
+ }
+ }
$gl.deleteTexture(texture_object.resource);
};
diff --git a/packages/webgpu/src/AtlasManager.ts b/packages/webgpu/src/AtlasManager.ts
index f53c9050..520e6ad9 100644
--- a/packages/webgpu/src/AtlasManager.ts
+++ b/packages/webgpu/src/AtlasManager.ts
@@ -102,15 +102,15 @@ export const $setAtlasCreator = (creator: AtlasCreator): void =>
*/
export const $getAtlasAttachmentObject = (): IAttachmentObject | null =>
{
- if (!($activeAtlasIndex in $atlasAttachmentObjects)) {
- if ($atlasCreator) {
- const attachment = $atlasCreator($activeAtlasIndex);
- $setAtlasAttachmentObject(attachment);
- } else {
+ let attachment = $atlasAttachmentObjects[$activeAtlasIndex];
+ if (!attachment) {
+ if (!$atlasCreator) {
return null;
}
+ attachment = $atlasCreator($activeAtlasIndex);
+ $setAtlasAttachmentObject(attachment);
}
- return $atlasAttachmentObjects[$activeAtlasIndex];
+ return attachment;
};
/**
@@ -121,10 +121,7 @@ export const $getAtlasAttachmentObject = (): IAttachmentObject | null =>
*/
export const $getAtlasAttachmentObjectByIndex = (index: number): IAttachmentObject | null =>
{
- if (!(index in $atlasAttachmentObjects)) {
- return null;
- }
- return $atlasAttachmentObjects[index];
+ return $atlasAttachmentObjects[index] ?? null;
};
/**
@@ -149,15 +146,17 @@ const $transferBounds: Float32Array[] = [];
*/
export const $getActiveTransferBounds = (index: number): Float32Array =>
{
- if (!(index in $transferBounds)) {
- $transferBounds[index] = new Float32Array([
+ let bounds = $transferBounds[index];
+ if (!bounds) {
+ bounds = new Float32Array([
$MAX_VALUE,
$MAX_VALUE,
$MIN_VALUE,
$MIN_VALUE
]);
+ $transferBounds[index] = bounds;
}
- return $transferBounds[index];
+ return bounds;
};
/**
diff --git a/packages/webgpu/src/Context.ts b/packages/webgpu/src/Context.ts
index 20927001..508293c0 100644
--- a/packages/webgpu/src/Context.ts
+++ b/packages/webgpu/src/Context.ts
@@ -2522,7 +2522,9 @@ export class Context
filter_bounds: Float32Array | null,
filter_params: Float32Array | null,
unique_key: string,
- filter_key: string
+ filter_key: string,
+ layer_scale_x: number = 1,
+ layer_scale_y: number = 1
): void {
this.drawArraysInstanced();
@@ -2558,7 +2560,9 @@ export class Context
contentSize.width,
contentSize.height,
this.$filterConfig,
- this.bufferManager
+ this.bufferManager,
+ layer_scale_x,
+ layer_scale_y
);
// メインのアタッチメントをバインド
diff --git a/packages/webgpu/src/Context/usecase/ContextContainerEndLayerUseCase.ts b/packages/webgpu/src/Context/usecase/ContextContainerEndLayerUseCase.ts
index 8ad48391..0108fe43 100644
--- a/packages/webgpu/src/Context/usecase/ContextContainerEndLayerUseCase.ts
+++ b/packages/webgpu/src/Context/usecase/ContextContainerEndLayerUseCase.ts
@@ -674,7 +674,11 @@ export const execute = (
_content_width: number,
_content_height: number,
config: ILocalFilterConfig,
- buffer_manager: BufferManager
+ buffer_manager: BufferManager,
+ // TODO: WebGPU版でも layer_scale による 1/layerScale 縮小合成に対応する。
+ // 現状はWebGL版のみで動作、WebGPU版は従来通り layer_scale=1 として合成される。
+ _layer_scale_x: number = 1,
+ _layer_scale_y: number = 1
): void => {
if (use_filter && matrix && filter_bounds && params) {
diff --git a/packages/webgpu/src/Context/usecase/ContextDrawArraysInstancedUseCase.ts b/packages/webgpu/src/Context/usecase/ContextDrawArraysInstancedUseCase.ts
index bd0051b4..767d44c5 100644
--- a/packages/webgpu/src/Context/usecase/ContextDrawArraysInstancedUseCase.ts
+++ b/packages/webgpu/src/Context/usecase/ContextDrawArraysInstancedUseCase.ts
@@ -14,15 +14,20 @@ import {
import { $getAtlasAttachmentObject } from "../../AtlasManager";
/**
- * @description キャッシュ済みバインドグループ
- * Cached bind group
+ * @description atlasViewをキーにしたBindGroupキャッシュ。複数アトラスを
+ * 交互に描画してもヒットする。textureが破棄されたら自動GC。
+ * BindGroup cache keyed by atlas view. Survives multi-atlas
+ * switching; entries are GC'd when the texture view is released.
*/
-let $cachedBindGroup: GPUBindGroup | null = null;
+let $bindGroupCache: WeakMap = new WeakMap();
/**
- * @description キャッシュ済みアトラステクスチャビュー
- * Cached atlas texture view
+ * @description キャッシュの整合性ガード。samplerまたはbindGroupLayoutが
+ * 切り替わった場合は全エントリを無効化する。
+ * Invalidates the entire cache when the sampler or bind
+ * group layout changes.
*/
-let $cachedAtlasView: GPUTextureView | null = null;
+let $cachedSampler: GPUSampler | null = null;
+let $cachedLayout: GPUBindGroupLayout | null = null;
/**
* @description ブレンドモードに応じたインスタンスパイプライン名を返す
@@ -176,10 +181,17 @@ export const execute = (
return null;
}
- // BindGroupキャッシュ: アトラスのテクスチャビューが同じなら再利用
+ // BindGroupキャッシュ: atlasView毎にBindGroupを保持。sampler/layoutが
+ // 切り替わった場合はキャッシュをクリアして整合性を保つ。
const atlasView = atlasAttachment.texture!.view;
- if (!$cachedBindGroup || $cachedAtlasView !== atlasView) {
- $cachedBindGroup = device.createBindGroup({
+ if ($cachedSampler !== sampler || $cachedLayout !== bindGroupLayout) {
+ $bindGroupCache = new WeakMap();
+ $cachedSampler = sampler;
+ $cachedLayout = bindGroupLayout;
+ }
+ let bindGroup = $bindGroupCache.get(atlasView);
+ if (!bindGroup) {
+ bindGroup = device.createBindGroup({
"layout": bindGroupLayout,
"entries": [
{
@@ -192,13 +204,13 @@ export const execute = (
}
]
});
- $cachedAtlasView = atlasView;
+ $bindGroupCache.set(atlasView, bindGroup);
}
// 描画
passEncoder.setVertexBuffer(0, vertexBuffer);
passEncoder.setVertexBuffer(1, instanceBuffer);
- passEncoder.setBindGroup(0, $cachedBindGroup);
+ passEncoder.setBindGroup(0, bindGroup);
passEncoder.draw(6, shaderManager.count, 0, 0);
// レンダーパスを終了
diff --git a/src/index.ts b/src/index.ts
index dec5f198..addc578a 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,7 +3,7 @@
import { Next2D } from "@next2d/core";
if (!("next2d" in window)) {
- console.log("%c Next2D Player %c 3.1.0 %c https://next2d.app",
+ console.log("%c Next2D Player %c 3.2.0 %c https://next2d.app",
"color: #fff; background: #5f5f5f",
"color: #fff; background: #4bc729",
"");