fix(image-pipeline): align detail-adjust grain seed parity
This commit is contained in:
@@ -8,6 +8,7 @@ uniform float uDenoiseLuma;
|
|||||||
uniform float uDenoiseColor;
|
uniform float uDenoiseColor;
|
||||||
uniform float uGrainAmount;
|
uniform float uGrainAmount;
|
||||||
uniform float uGrainScale;
|
uniform float uGrainScale;
|
||||||
|
uniform float uImageWidth;
|
||||||
|
|
||||||
float pseudoNoise(float seed) {
|
float pseudoNoise(float seed) {
|
||||||
float x = sin(seed * 12.9898) * 43758.5453;
|
float x = sin(seed * 12.9898) * 43758.5453;
|
||||||
@@ -36,7 +37,10 @@ void main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (uGrainAmount > 0.0) {
|
if (uGrainAmount > 0.0) {
|
||||||
float grainSeed = (gl_FragCoord.y * 4096.0 + gl_FragCoord.x) / max(0.5, uGrainScale);
|
float pixelX = floor(gl_FragCoord.x);
|
||||||
|
float pixelY = floor(gl_FragCoord.y);
|
||||||
|
float pixelIndex = ((pixelY * max(1.0, uImageWidth)) + pixelX) * 4.0;
|
||||||
|
float grainSeed = (pixelIndex + 1.0) / max(0.5, uGrainScale);
|
||||||
float grain = (pseudoNoise(grainSeed) - 0.5) * uGrainAmount * 40.0;
|
float grain = (pseudoNoise(grainSeed) - 0.5) * uGrainAmount * 40.0;
|
||||||
rgb += vec3(grain);
|
rgb += vec3(grain);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -274,6 +274,11 @@ function applyStepUniforms(
|
|||||||
if (grainScaleLocation) {
|
if (grainScaleLocation) {
|
||||||
gl.uniform1f(grainScaleLocation, Math.max(0.5, detail.grain.size));
|
gl.uniform1f(grainScaleLocation, Math.max(0.5, detail.grain.size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const imageWidthLocation = gl.getUniformLocation(shaderProgram, "uImageWidth");
|
||||||
|
if (imageWidthLocation) {
|
||||||
|
gl.uniform1f(imageWidthLocation, request.width);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -539,6 +539,7 @@ function runLightAdjustShader(
|
|||||||
|
|
||||||
function runDetailAdjustShader(
|
function runDetailAdjustShader(
|
||||||
input: Uint8Array,
|
input: Uint8Array,
|
||||||
|
width: number,
|
||||||
uniforms: Map<string, number | [number, number, number]>,
|
uniforms: Map<string, number | [number, number, number]>,
|
||||||
): Uint8Array {
|
): Uint8Array {
|
||||||
const output = new Uint8Array(input.length);
|
const output = new Uint8Array(input.length);
|
||||||
@@ -549,6 +550,7 @@ function runDetailAdjustShader(
|
|||||||
const denoiseColor = Number(uniforms.get("uDenoiseColor") ?? 0);
|
const denoiseColor = Number(uniforms.get("uDenoiseColor") ?? 0);
|
||||||
const grainAmount = Number(uniforms.get("uGrainAmount") ?? 0);
|
const grainAmount = Number(uniforms.get("uGrainAmount") ?? 0);
|
||||||
const grainScale = Math.max(0.5, Number(uniforms.get("uGrainScale") ?? 1));
|
const grainScale = Math.max(0.5, Number(uniforms.get("uGrainScale") ?? 1));
|
||||||
|
const imageWidth = Math.max(1, Number(uniforms.get("uImageWidth") ?? width));
|
||||||
|
|
||||||
for (let index = 0; index < input.length; index += 4) {
|
for (let index = 0; index < input.length; index += 4) {
|
||||||
let red = input[index] ?? 0;
|
let red = input[index] ?? 0;
|
||||||
@@ -580,7 +582,11 @@ function runDetailAdjustShader(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (grainAmount > 0) {
|
if (grainAmount > 0) {
|
||||||
const grain = (pseudoNoise((index + 1) / grainScale) - 0.5) * grainAmount * 40;
|
const pixel = index / 4;
|
||||||
|
const x = pixel % imageWidth;
|
||||||
|
const y = Math.floor(pixel / imageWidth);
|
||||||
|
const pixelIndex = (y * imageWidth + x) * 4;
|
||||||
|
const grain = (pseudoNoise((pixelIndex + 1) / grainScale) - 0.5) * grainAmount * 40;
|
||||||
red += grain;
|
red += grain;
|
||||||
green += grain;
|
green += grain;
|
||||||
blue += grain;
|
blue += grain;
|
||||||
@@ -798,7 +804,11 @@ function createParityWebglContext(): WebGLRenderingContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (currentProgram.kind === "detail-adjust") {
|
if (currentProgram.kind === "detail-adjust") {
|
||||||
currentFramebuffer.attachment.data = runDetailAdjustShader(sourceTexture.data, currentProgram.uniforms);
|
currentFramebuffer.attachment.data = runDetailAdjustShader(
|
||||||
|
sourceTexture.data,
|
||||||
|
drawWidth,
|
||||||
|
currentProgram.uniforms,
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,19 @@ function createUnsupportedStep(): PipelineStep {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createDetailAdjustStep(): PipelineStep {
|
||||||
|
return {
|
||||||
|
nodeId: "detail-1",
|
||||||
|
type: "detail-adjust",
|
||||||
|
params: {
|
||||||
|
sharpen: { amount: 20, radius: 1.4, detail: 10, masking: 0 },
|
||||||
|
clarity: 15,
|
||||||
|
denoise: { luminance: 12, color: 18, detail: 50 },
|
||||||
|
grain: { amount: 35, size: 2.5, roughness: 50 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
describe("webgl backend poc", () => {
|
describe("webgl backend poc", () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.resetModules();
|
vi.resetModules();
|
||||||
@@ -425,6 +438,29 @@ describe("webgl backend poc", () => {
|
|||||||
expect(getContextSpy).toHaveBeenCalledWith("webgl2", expect.any(Object));
|
expect(getContextSpy).toHaveBeenCalledWith("webgl2", expect.any(Object));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes image width uniform for detail-adjust grain parity", async () => {
|
||||||
|
const fakeGl = createFakeWebglContext({
|
||||||
|
readbackPixels: new Uint8Array([11, 22, 33, 255]),
|
||||||
|
});
|
||||||
|
vi.spyOn(HTMLCanvasElement.prototype, "getContext").mockImplementation((contextId) => {
|
||||||
|
if (contextId === "webgl") {
|
||||||
|
return fakeGl;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
const { createWebglPreviewBackend } = await import("@/lib/image-pipeline/backend/webgl/webgl-backend");
|
||||||
|
const backend = createWebglPreviewBackend();
|
||||||
|
backend.runPreviewStep({
|
||||||
|
pixels: new Uint8ClampedArray(7 * 3 * 4),
|
||||||
|
step: createDetailAdjustStep(),
|
||||||
|
width: 7,
|
||||||
|
height: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(fakeGl.uniform1f).toHaveBeenCalledWith(expect.anything(), 7);
|
||||||
|
});
|
||||||
|
|
||||||
it("downgrades compile/link failures to cpu with runtime_error reason", async () => {
|
it("downgrades compile/link failures to cpu with runtime_error reason", async () => {
|
||||||
const { createBackendRouter } = await import("@/lib/image-pipeline/backend/backend-router");
|
const { createBackendRouter } = await import("@/lib/image-pipeline/backend/backend-router");
|
||||||
const { createWebglPreviewBackend } = await import("@/lib/image-pipeline/backend/webgl/webgl-backend");
|
const { createWebglPreviewBackend } = await import("@/lib/image-pipeline/backend/webgl/webgl-backend");
|
||||||
|
|||||||
Reference in New Issue
Block a user