Skip to content

Commit 53d8feb

Browse files
committed
Fix denoiser renderer race condition. Refactor multi pass renderer. Add init method to denoiser interface.
1 parent 3580f75 commit 53d8feb

File tree

5 files changed

+109
-128
lines changed

5 files changed

+109
-128
lines changed

src/main/java/de/lemaik/chunky/denoiser/DenoisedPathTracingRenderer.java

Lines changed: 41 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.io.*;
1111
import java.nio.ByteOrder;
12+
import java.nio.file.Files;
1213

1314
public class DenoisedPathTracingRenderer extends MultiPassRenderer {
1415
protected final DenoiserSettings settings;
@@ -22,8 +23,6 @@ public class DenoisedPathTracingRenderer extends MultiPassRenderer {
2223
protected final AlbedoTracer albedoTracer = new AlbedoTracer();
2324
protected final NormalTracer normalTracer;
2425

25-
private boolean hiddenPasses = false;
26-
2726
public DenoisedPathTracingRenderer(DenoiserSettings settings, Denoiser denoiser,
2827
String id, String name, String description, RayTracer tracer) {
2928
this.settings = settings;
@@ -56,81 +55,68 @@ public String getDescription() {
5655
public void render(DefaultRenderManager manager) throws InterruptedException {
5756
Scene scene = manager.bufferedScene;
5857
double[] sampleBuffer = scene.getSampleBuffer();
59-
boolean aborted = false;
60-
61-
int originalSpp = scene.spp;
62-
int sceneTarget = scene.getTargetSpp();
63-
64-
int maxSpp = Math.max(sceneTarget, Math.max(settings.albedoSpp.get(), settings.normalSpp.get()));
65-
scene.setTargetSpp(maxSpp);
66-
67-
RayTracer[] tracers = new RayTracer[] {albedoTracer, normalTracer, tracer};
68-
float[][] buffers = new float[][] {
69-
settings.renderAlbedo.get() ? new float[sampleBuffer.length] : null,
70-
settings.renderNormal.get() ? new float[sampleBuffer.length] : null,
71-
null};
72-
boolean[] tracerMask = new boolean[3];
73-
scene.spp = 0;
74-
75-
while (scene.spp < maxSpp) {
76-
tracerMask[0] = settings.renderAlbedo.get() && scene.spp < settings.albedoSpp.get();
77-
tracerMask[1] = settings.renderNormal.get() && scene.spp < settings.normalSpp.get();
78-
tracerMask[2] = scene.spp >= originalSpp && scene.spp < sceneTarget;
79-
hiddenPasses = !tracerMask[2];
80-
renderPass(manager, manager.context.sppPerPass(), tracers, buffers, tracerMask);
81-
if (scene.spp < maxSpp && postRender.getAsBoolean()) {
82-
aborted = true;
83-
break;
58+
59+
boolean albedoEnable = settings.renderAlbedo.get();
60+
int albedoTarget = settings.albedoSpp.get();
61+
int albedoSampleScale = (int) Math.ceil((double) albedoTarget / scene.getTargetSpp());
62+
int albedoSamples = 0;
63+
float[] albedoBuffer = albedoEnable ? new float[sampleBuffer.length] : null;
64+
65+
boolean normalEnable = settings.renderNormal.get();
66+
int normalTarget = settings.normalSpp.get();
67+
int normalSampleScale = (int) Math.ceil((double) normalTarget / scene.getTargetSpp());
68+
int normalSamples = 0;
69+
float[] normalBuffer = normalEnable ? new float[sampleBuffer.length] : null;
70+
71+
while (scene.spp < scene.getTargetSpp()) {
72+
if (albedoEnable && albedoSamples < albedoTarget) {
73+
int samples = Math.min(albedoSampleScale, albedoTarget - albedoSamples);
74+
albedoSamples = this.renderPass(manager, albedoSamples, samples, albedoTracer, albedoBuffer);
75+
}
76+
if (normalEnable && normalSamples < normalTarget) {
77+
int samples = Math.max(normalSampleScale, normalTarget - normalSamples);
78+
normalSamples = this.renderPass(manager, normalSamples, samples, normalTracer, normalBuffer);
79+
}
80+
scene.spp = renderPass(manager, scene.spp, 1, tracer, null);
81+
82+
if (scene.spp < scene.getTargetSpp() && postRender.getAsBoolean()) {
83+
// Canceled
84+
return;
8485
}
8586
}
8687

87-
if (!aborted && settings.saveBeauty.get()) {
88+
if (settings.saveBeauty.get()) {
8889
File out = manager.context.getSceneFile(scene.name + ".beauty.pfm");
8990
scene.saveFrame(out, PortableFloatMap.getPfmExportFormat(),
9091
TaskTracker.NONE, manager.context.numRenderThreads());
9192
}
9293

93-
if (!aborted && settings.saveAlbedo.get()) {
94+
if (settings.saveAlbedo.get()) {
9495
File out = manager.context.getSceneFile(scene.name + ".albedo.pfm");
95-
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(out))) {
96-
PortableFloatMap.writeImage(buffers[0], scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
96+
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(out.toPath()))) {
97+
PortableFloatMap.writeImage(albedoBuffer, scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
9798
} catch (IOException e) {
9899
Log.error("Failed to save albedo pass", e);
99100
}
100101
}
101102

102-
if (!aborted && settings.saveNormal.get()) {
103+
if (settings.saveNormal.get()) {
103104
File out = manager.context.getSceneFile(scene.name + ".normal.pfm");
104-
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(out))) {
105-
PortableFloatMap.writeImage(buffers[1], scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
105+
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(out.toPath()))) {
106+
PortableFloatMap.writeImage(normalBuffer, scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
106107
} catch (IOException e) {
107108
Log.error("Failed to save normal pass", e);
108109
}
109110
}
110111

111-
if (!aborted) {
112-
if (denoiser instanceof OidnBinaryDenoiser)
113-
((OidnBinaryDenoiser) denoiser).loadPath();
114-
115-
try {
116-
denoiser.denoiseDouble(scene.width, scene.height, sampleBuffer,
117-
buffers[0], buffers[1], sampleBuffer);
118-
} catch (Denoiser.DenoisingFailedException e) {
119-
Log.error("Failed to denoise", e);
120-
}
112+
try {
113+
manager.getRenderTask().update("Denoising", scene.getTargetSpp(), scene.spp);
114+
denoiser.init();
115+
denoiser.denoiseDouble(scene.width, scene.height, sampleBuffer, albedoBuffer, normalBuffer, sampleBuffer);
116+
} catch (Denoiser.DenoisingFailedException e) {
117+
Log.error("Failed to denoise", e);
121118
}
122119

123-
if (scene.spp < originalSpp) {
124-
scene.spp = originalSpp;
125-
} else if (scene.spp > sceneTarget) {
126-
scene.spp = sceneTarget;
127-
}
128-
scene.setTargetSpp(sceneTarget);
129120
postRender.getAsBoolean();
130121
}
131-
132-
@Override
133-
public boolean autoPostProcess() {
134-
return !hiddenPasses;
135-
}
136122
}

src/main/java/de/lemaik/chunky/denoiser/Denoiser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ default void denoiseDouble(int width, int height, double[] beauty, float[] albed
3535
output[i] = outputF[i];
3636
}
3737

38+
/**
39+
* Initialize this denoiser and prepare it for denoising.
40+
*/
41+
default void init() { }
42+
3843
/**
3944
* Denoising failed for some reason.
4045
*/

src/main/java/de/lemaik/chunky/denoiser/DenoiserPassRenderer.java

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import java.io.*;
1111
import java.nio.ByteOrder;
12+
import java.nio.file.Files;
1213

1314
public class DenoiserPassRenderer extends MultiPassRenderer {
1415
protected final DenoiserSettings settings;
@@ -57,65 +58,66 @@ public boolean autoPostProcess() {
5758
public void render(DefaultRenderManager manager) throws InterruptedException {
5859
Scene scene = manager.bufferedScene;
5960
double[] sampleBuffer = scene.getSampleBuffer();
60-
boolean aborted = false;
61-
62-
scene.setTargetSpp(Math.max(settings.albedoSpp.get(), settings.normalSpp.get()));
63-
64-
RayTracer[] tracers = new RayTracer[] {albedoTracer, normalTracer};
65-
float[][] buffers = new float[][] {
66-
settings.renderAlbedo.get() ? new float[sampleBuffer.length] : null,
67-
settings.renderNormal.get() ? new float[sampleBuffer.length] : null,
68-
};
69-
boolean[] tracerMask = new boolean[2];
70-
scene.spp = 0;
71-
72-
while (scene.spp < scene.getTargetSpp()) {
73-
tracerMask[0] = settings.renderAlbedo.get() && scene.spp < settings.albedoSpp.get();
74-
tracerMask[1] = settings.renderNormal.get() && scene.spp < settings.normalSpp.get();
75-
renderPass(manager, manager.context.sppPerPass(), tracers, buffers, tracerMask);
76-
if (scene.spp < scene.getTargetSpp() && postRender.getAsBoolean()) {
77-
aborted = true;
78-
break;
61+
62+
boolean albedoEnable = settings.renderAlbedo.get();
63+
int albedoTarget = settings.albedoSpp.get();
64+
float[] albedoBuffer = albedoEnable ? new float[sampleBuffer.length] : null;
65+
66+
boolean normalEnable = settings.renderNormal.get();
67+
int normalTarget = settings.normalSpp.get();
68+
float[] normalBuffer = normalEnable ? new float[sampleBuffer.length] : null;
69+
70+
if (albedoEnable || normalEnable) {
71+
int targetSpp = Math.max(albedoTarget, normalTarget);
72+
scene.spp = 0;
73+
74+
while (scene.spp < targetSpp) {
75+
if (albedoEnable && scene.spp < albedoTarget) {
76+
this.renderPass(manager, scene.spp, 1, albedoTracer, albedoBuffer);
77+
}
78+
if (normalEnable && scene.spp < normalTarget) {
79+
this.renderPass(manager, scene.spp, 1, normalTracer, normalBuffer);
80+
}
81+
scene.spp += 1;
82+
if (scene.spp < targetSpp && postRender.getAsBoolean()) {
83+
// Canceled
84+
return;
85+
}
7986
}
8087
}
8188

82-
if (!aborted && settings.saveBeauty.get()) {
89+
if (settings.saveBeauty.get()) {
8390
File out = manager.context.getSceneFile(scene.name + ".beauty.pfm");
8491
scene.saveFrame(out, PortableFloatMap.getPfmExportFormat(),
8592
TaskTracker.NONE, manager.context.numRenderThreads());
8693
}
8794

88-
if (!aborted && settings.saveAlbedo.get()) {
95+
if (settings.saveAlbedo.get()) {
8996
File out = manager.context.getSceneFile(scene.name + ".albedo.pfm");
90-
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(out))) {
91-
PortableFloatMap.writeImage(buffers[0], scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
97+
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(out.toPath()))) {
98+
PortableFloatMap.writeImage(albedoBuffer, scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
9299
} catch (IOException e) {
93100
Log.error("Failed to save albedo pass", e);
94101
}
95102
}
96103

97-
if (!aborted && settings.saveNormal.get()) {
104+
if (settings.saveNormal.get()) {
98105
File out = manager.context.getSceneFile(scene.name + ".normal.pfm");
99-
try (OutputStream os = new BufferedOutputStream(new FileOutputStream(out))) {
100-
PortableFloatMap.writeImage(buffers[1], scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
106+
try (OutputStream os = new BufferedOutputStream(Files.newOutputStream(out.toPath()))) {
107+
PortableFloatMap.writeImage(normalBuffer, scene.width, scene.height, ByteOrder.LITTLE_ENDIAN, os);
101108
} catch (IOException e) {
102109
Log.error("Failed to save normal pass", e);
103110
}
104111
}
105112

106-
if (!aborted) {
107-
if (denoiser instanceof OidnBinaryDenoiser)
108-
((OidnBinaryDenoiser) denoiser).loadPath();
109-
110-
try {
111-
denoiser.denoiseDouble(scene.width, scene.height, sampleBuffer,
112-
buffers[0], buffers[1], sampleBuffer);
113-
114-
scene.spp = scene.getTargetSpp();
115-
postRender.getAsBoolean();
116-
} catch (Denoiser.DenoisingFailedException e) {
117-
Log.error("Failed to denoise", e);
118-
}
113+
try {
114+
manager.getRenderTask().update("Denoising", scene.getTargetSpp(), scene.spp);
115+
denoiser.init();
116+
denoiser.denoiseDouble(scene.width, scene.height, sampleBuffer, albedoBuffer, normalBuffer, sampleBuffer);
117+
} catch (Denoiser.DenoisingFailedException e) {
118+
Log.error("Failed to denoise", e);
119119
}
120+
121+
postRender.getAsBoolean();
120122
}
121123
}

src/main/java/de/lemaik/chunky/denoiser/MultiPassRenderer.java

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import se.llbit.chunky.renderer.scene.Scene;
88

99
public abstract class MultiPassRenderer extends TileBasedRenderer {
10-
protected void renderPass(DefaultRenderManager manager, int passSpp, RayTracer[] tracers, float[][] renderBuffers, boolean[] tracerMask) throws InterruptedException {
10+
protected int renderPass(DefaultRenderManager manager, int spp, int passSamples, RayTracer tracer, float[] buffer) throws InterruptedException {
1111
Scene scene = manager.bufferedScene;
1212
double[] sampleBuffer = scene.getSampleBuffer();
1313
int width = scene.width;
@@ -17,56 +17,39 @@ protected void renderPass(DefaultRenderManager manager, int passSpp, RayTracer[]
1717
double halfWidth = width / (2.0 * height);
1818
double invHeight = 1.0 / height;
1919

20-
int spp = scene.spp;
21-
double passinv = 1.0 / passSpp;
22-
double sinv = 1.0 / (passSpp + spp);
20+
double sinv = 1.0 / (passSamples + spp);
2321

2422
submitTiles(manager, (state, pixel) -> {
2523
int x = pixel.firstInt();
2624
int y = pixel.secondInt();
2725

28-
double[] srgb = new double[tracers.length * 3];
26+
double[] srgb = new double[3];
2927

30-
for (int k = 0; k < passSpp; k++) {
28+
for (int k = 0; k < passSamples; k++) {
3129
double ox = state.random.nextDouble();
3230
double oy = state.random.nextDouble();
3331

34-
for (int i = 0; i < tracers.length; i++) {
35-
if (tracerMask[i]) {
36-
cam.calcViewRay(state.ray, state.random,
37-
-halfWidth + (x + ox) * invHeight,
38-
-0.5 + (y + oy) * invHeight);
32+
cam.calcViewRay(state.ray, state.random,
33+
-halfWidth + (x + ox) * invHeight,
34+
-0.5 + (y + oy) * invHeight);
3935

40-
scene.rayTrace(tracers[i], state);
41-
srgb[i * 3 + 0] += state.ray.color.x;
42-
srgb[i * 3 + 1] += state.ray.color.y;
43-
srgb[i * 3 + 2] += state.ray.color.z;
44-
}
45-
}
36+
scene.rayTrace(tracer, state);
37+
srgb[0] += state.ray.color.x;
38+
srgb[1] += state.ray.color.y;
39+
srgb[2] += state.ray.color.z;
4640
}
4741

4842
int offset = 3 * (y*width + x);
49-
for (int i = 0; i < tracers.length; i++) {
50-
if (tracerMask[i]) {
51-
float[] buffer = renderBuffers[i];
52-
double r = srgb[i * 3 + 0] * passinv;
53-
double g = srgb[i * 3 + 1] * passinv;
54-
double b = srgb[i * 3 + 2] * passinv;
55-
56-
if (buffer == null) {
57-
sampleBuffer[offset + 0] = (sampleBuffer[offset + 0] * spp + r) * sinv;
58-
sampleBuffer[offset + 1] = (sampleBuffer[offset + 1] * spp + g) * sinv;
59-
sampleBuffer[offset + 2] = (sampleBuffer[offset + 2] * spp + b) * sinv;
60-
} else {
61-
buffer[offset + 0] = (float) ((buffer[offset + 0] * spp + r) * sinv);
62-
buffer[offset + 1] = (float) ((buffer[offset + 1] * spp + g) * sinv);
63-
buffer[offset + 2] = (float) ((buffer[offset + 2] * spp + b) * sinv);
64-
}
43+
for (int i = 0; i < 3; i++) {
44+
if (buffer == null) {
45+
sampleBuffer[offset + i] = (sampleBuffer[offset + i] * spp + srgb[i]) * sinv;
46+
} else {
47+
buffer[offset + i] = (float) ((buffer[offset + i] * spp + srgb[i]) * sinv);
6548
}
6649
}
6750
});
6851

6952
manager.pool.awaitEmpty();
70-
scene.spp += passSpp;
53+
return spp + passSamples;
7154
}
7255
}

src/main/java/de/lemaik/chunky/denoiser/OidnBinaryDenoiser.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public void loadPath() {
2424
setOidnPath(PersistentSettings.settings.getString("oidnPath", ""));
2525
}
2626

27+
@Override
28+
public void init() {
29+
this.loadPath();
30+
}
31+
2732
@Override
2833
public float[] denoise(int width, int height, float[] beauty, float[] albedo, float[] normal)
2934
throws DenoisingFailedException{

0 commit comments

Comments
 (0)