Skip to content

Commit 3f7527a

Browse files
jeannekamikaze3gg
andauthored
Fix depth in SDSM shadows (jMonkeyEngine#2630)
* Fix jMonkeyEngine#2620 * Address feedback * Address more feedback * Use GLSL compat * Fix shader --------- Co-authored-by: 3gg <3gg@shellblade.net>
1 parent 9f087c0 commit 3f7527a

File tree

9 files changed

+159
-42
lines changed

9 files changed

+159
-42
lines changed

jme3-core/src/main/java/com/jme3/renderer/opengl/ComputeShader.java

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,25 +50,55 @@
5050
public class ComputeShader extends NativeObject {
5151

5252
private final GL4 gl;
53+
// The source need not be stored, but it helps with debugging.
5354
private final String source;
55+
5456
/**
5557
* Creates a new compute shader from GLSL source code.
5658
*/
5759
public ComputeShader(GL4 gl, String source) {
5860
super();
5961
this.gl = gl;
6062
this.source = source;
61-
//Load this upfront to surface any problems at init time
63+
// Load this up front to surface any problems at init time.
64+
createComputeShader();
65+
}
66+
67+
/**
68+
* Creates a new compute shader from GLSL source code and a set of defines.
69+
*
70+
* @param defines An array of string pairs. The first element of the pair
71+
* is the macro name; the second element, the definition.
72+
*/
73+
public ComputeShader(GL4 gl, String source, String[][] defines) {
74+
super();
75+
this.gl = gl;
76+
this.source = addDefines(source, defines);
77+
// Load this up front to surface any problems at init time.
6278
createComputeShader();
6379
}
64-
private ComputeShader(ComputeShader source){
80+
81+
private ComputeShader(ComputeShader source) {
6582
super();
6683
this.gl = source.gl;
6784
this.id = source.id;
6885
this.source = null;
6986
}
7087

71-
private void createComputeShader(){
88+
private String addDefines(String source, String[][] defines) {
89+
// The #version pragma must appear before anything else. Insert the
90+
// defines after it.
91+
String[] sourceLines = (String[])source.split("\\r?\\n", 2);
92+
StringBuilder builder = new StringBuilder();
93+
builder.append(sourceLines[0] + "\n");
94+
for (String[] pair : defines) {
95+
builder.append("#define " + pair[0] + " " + pair[1] + "\n");
96+
}
97+
builder.append(sourceLines[1] + "\n");
98+
return builder.toString();
99+
}
100+
101+
private void createComputeShader() {
72102
// Create and compile the shader
73103
int shaderId = gl.glCreateShader(GL4.GL_COMPUTE_SHADER);
74104
if (shaderId <= 0) {

jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowRenderer.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ private void initGL() {
147147

148148
sdsmFitter = new SdsmFitter(gl4, renderer, assetManager);
149149
glInitialized = true;
150-
151150
}
152151

153152
/**

jme3-core/src/main/java/com/jme3/shadow/SdsmFitter.java

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,16 @@ public class SdsmFitter {
5858

5959
private final GL4 gl4;
6060
private final Renderer renderer;
61+
private final AssetManager assetManager;
6162
private int maxFrameLag = 3;
6263

63-
private final ComputeShader depthReduceShader;
64-
private final ComputeShader fitFrustumsShader;
64+
// Because the multisampling status of the depth texture may change anytime,
65+
// we store both ms and no-ms versions of the shaders, both initialized
66+
// lazily. This avoids recompiling the same set of shaders twice and also
67+
// compiling unnecessary shaders if the multisampling status does not
68+
// change.
69+
private InternalShaders shadersNoMultisampling;
70+
private InternalShaders shadersMultisampling;
6571

6672
private final LinkedList<SdsmResultHolder> resultHoldersInFlight = new LinkedList<>();
6773
private final LinkedList<SdsmResultHolder> resultHoldersReady = new LinkedList<>();
@@ -310,18 +316,58 @@ void cleanup() {
310316
}
311317
}
312318

319+
/**
320+
* Manages the pair of depth-reduce and fit-frustums shaders.
321+
*/
322+
private class InternalShaders {
323+
public final ComputeShader depthReduceShader;
324+
public final ComputeShader fitFrustumsShader;
325+
326+
InternalShaders(AssetManager assetManager, boolean multisampling) {
327+
String reduceSource = (String)assetManager.loadAsset(REDUCE_DEPTH_SHADER);
328+
String fitSource = (String)assetManager.loadAsset(FIT_FRUSTUMS_SHADER);
329+
330+
depthReduceShader = buildShader(reduceSource, multisampling);
331+
fitFrustumsShader = buildShader(fitSource, multisampling);
332+
}
333+
334+
private ComputeShader buildShader(String source, boolean multisampling) {
335+
ComputeShader shader =
336+
multisampling
337+
? new ComputeShader(gl4, source, new String[][]{{"RESOLVE_DEPTH_MS", "1"}})
338+
: new ComputeShader(gl4, source);
339+
renderer.registerNativeObject(shader);
340+
return shader;
341+
}
342+
343+
/**
344+
* Cleans up GPU resources.
345+
*/
346+
public void cleanup(Renderer renderer) {
347+
depthReduceShader.deleteObject(renderer);
348+
fitFrustumsShader.deleteObject(renderer);
349+
}
350+
}
351+
313352
public SdsmFitter(GL4 gl, Renderer renderer, AssetManager assetManager) {
314353
this.gl4 = gl;
315354
this.renderer = renderer;
316-
317-
// Load compute shaders
318-
String reduceSource = (String)assetManager.loadAsset(REDUCE_DEPTH_SHADER);
319-
String fitSource = (String)assetManager.loadAsset(FIT_FRUSTUMS_SHADER);
320-
321-
depthReduceShader = new ComputeShader(gl, reduceSource);
322-
renderer.registerNativeObject(depthReduceShader);
323-
fitFrustumsShader = new ComputeShader(gl, fitSource);
324-
renderer.registerNativeObject(fitFrustumsShader);
355+
this.assetManager = assetManager;
356+
}
357+
358+
private InternalShaders initShaders(Texture depthTexture) {
359+
boolean multisampling = depthTexture.getImage().getMultiSamples() > 1;
360+
if (multisampling) {
361+
if (shadersMultisampling == null) {
362+
shadersMultisampling = new InternalShaders(assetManager, true);
363+
}
364+
return shadersMultisampling;
365+
} else {
366+
if (shadersNoMultisampling == null) {
367+
shadersNoMultisampling = new InternalShaders(assetManager, false);
368+
}
369+
return shadersNoMultisampling;
370+
}
325371
}
326372

327373
/**
@@ -336,6 +382,12 @@ public SdsmFitter(GL4 gl, Renderer renderer, AssetManager assetManager) {
336382
public void fit(Texture depthTexture, int splitCount, Matrix4f cameraToLight,
337383
float cameraNear, float cameraFar) {
338384

385+
int depthMultiSamples = depthTexture.getImage().getMultiSamples();
386+
387+
InternalShaders shaders = initShaders(depthTexture);
388+
ComputeShader depthReduceShader = shaders.depthReduceShader;
389+
ComputeShader fitFrustumsShader = shaders.fitFrustumsShader;
390+
339391
SdsmResultHolder holder = getResultHolderForUse();
340392
holder.parameters = new FitParameters(cameraToLight, splitCount, cameraNear, cameraFar);
341393

@@ -360,6 +412,8 @@ public void fit(Texture depthTexture, int splitCount, Matrix4f cameraToLight,
360412
} catch (TextureUnitException e) {
361413
throw new RendererException(e);
362414
}
415+
int loc = depthReduceShader.getUniformLocation("m_NumSamplesDepth");
416+
depthReduceShader.setUniform(loc, depthMultiSamples);
363417
depthReduceShader.bindShaderStorageBuffer(1, holder.minMaxDepthSsbo);
364418
depthReduceShader.dispatch(xGroups, yGroups, 1);
365419
gl4.glMemoryBarrier(GL4.GL_SHADER_STORAGE_BARRIER_BIT);
@@ -373,6 +427,8 @@ public void fit(Texture depthTexture, int splitCount, Matrix4f cameraToLight,
373427
} catch (TextureUnitException e) {
374428
throw new RendererException(e);
375429
}
430+
loc = fitFrustumsShader.getUniformLocation("m_NumSamplesDepth");
431+
fitFrustumsShader.setUniform(loc, depthMultiSamples);
376432
fitFrustumsShader.bindShaderStorageBuffer(1, holder.minMaxDepthSsbo);
377433
fitFrustumsShader.bindShaderStorageBuffer(2, holder.fitFrustumSsbo);
378434

@@ -437,11 +493,11 @@ public void cleanup() {
437493
}
438494
resultHoldersReady.clear();
439495

440-
if (depthReduceShader != null) {
441-
depthReduceShader.deleteObject(renderer);
496+
if (shadersMultisampling != null) {
497+
shadersMultisampling.cleanup(renderer);
442498
}
443-
if (fitFrustumsShader != null) {
444-
fitFrustumsShader.deleteObject(renderer);
499+
if (shadersNoMultisampling != null) {
500+
shadersNoMultisampling.cleanup(renderer);
445501
}
446502
}
447503

jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/FitLightFrustums.comp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
#version 430
22

3+
#import "Common/ShaderLib/GLSLCompat.glsllib"
4+
#import "Common/ShaderLib/MultiSample.glsllib"
5+
36
/**
47
* Computes tight bounding boxes for each shadow cascade via min/max on lightspace locations of depth samples that fall within each cascade.
58
*/
69

710
layout(local_size_x = 16, local_size_y = 16) in;
811

9-
layout(binding = 0) uniform sampler2D inputDepth;
12+
layout(binding = 0) uniform DEPTHTEXTURE inputDepth;
1013

1114
layout(std430, binding = 1) readonly buffer MinMaxBuffer {
1215
uint gMin;
@@ -103,7 +106,7 @@ void main() {
103106
ivec2 gid = ivec2(gl_GlobalInvocationID.xy);
104107
ivec2 lid = ivec2(gl_LocalInvocationID.xy);
105108
uint tid = gl_LocalInvocationIndex;
106-
ivec2 inputSize = textureSize(inputDepth, 0);
109+
ivec2 inputSize = getTextureSize(inputDepth);
107110
ivec2 baseCoord = gid * 2;
108111

109112
// Initialize local bounds to infinity
@@ -126,11 +129,11 @@ void main() {
126129
for (int x = 0; x < 2; x++) {
127130
ivec2 coord = baseCoord + ivec2(x, y);
128131
if (coord.x < inputSize.x && coord.y < inputSize.y) {
129-
float depth = texelFetch(inputDepth, coord, 0).r;
132+
float depth = getDepthMax(inputDepth, coord, m_NumSamplesDepth).r;
130133
// Skip background (depth == 1.0)
131134
if (depth != 1.0) {
132135
// Reconstruct clip-space position from depth
133-
vec2 uv = (vec2(coord) + 0.5) / vec2(textureSize(inputDepth, 0));
136+
vec2 uv = (vec2(coord) + 0.5) / vec2(getTextureSize(inputDepth));
134137
vec4 clipPos = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0);
135138

136139
// Transform to light view space

jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/ReduceDepth.comp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
#version 430
22

3+
#import "Common/ShaderLib/GLSLCompat.glsllib"
4+
#import "Common/ShaderLib/MultiSample.glsllib"
5+
36
/**
47
* Finds the global minimum/maximum values of a depth texture.
58
*/
69

710
layout(local_size_x = 16, local_size_y = 16) in;
811

9-
layout(binding = 0) uniform sampler2D inputDepth;
12+
layout(binding = 0) uniform DEPTHTEXTURE inputDepth;
1013

1114
layout(std430, binding = 1) buffer MinMaxBuffer {
1215
uint gMin;
@@ -33,7 +36,7 @@ void main() {
3336
ivec2 gid = ivec2(gl_GlobalInvocationID.xy);
3437
ivec2 lid = ivec2(gl_LocalInvocationID.xy);
3538
uint tid = gl_LocalInvocationIndex;
36-
ivec2 inputSize = textureSize(inputDepth, 0);
39+
ivec2 inputSize = getTextureSize(inputDepth);
3740

3841
// Each thread samples a 2x2 block
3942
ivec2 baseCoord = gid * 2;
@@ -43,7 +46,7 @@ void main() {
4346
for (int x = 0; x < 2; x++) {
4447
ivec2 coord = baseCoord + ivec2(x, y);
4548
if (coord.x < inputSize.x && coord.y < inputSize.y) {
46-
float depth = texelFetch(inputDepth, coord, 0).r;
49+
float depth = getDepthMax(inputDepth, coord, m_NumSamplesDepth).r;
4750
// Discard depth == 1.0 (background/sky)
4851
if (depth != 1.0) {
4952
minMax.x = min(minMax.x, depth);

jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.frag

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
#import "Common/ShaderLib/GLSLCompat.glsllib"
2+
#import "Common/ShaderLib/MultiSample.glsllib"
23
#import "Common/ShaderLib/Shadows.glsllib"
34

45
//Stripped version of the usual shadow fragment shader for SDSM; it intentionally leaves out some features.
5-
uniform sampler2D m_Texture;
6-
uniform sampler2D m_DepthTexture;
6+
uniform COLORTEXTURE m_Texture;
7+
uniform DEPTHTEXTURE m_DepthTexture;
78
uniform mat4 m_ViewProjectionMatrixInverse;
89
uniform vec4 m_ViewProjectionMatrixRow2;
910

@@ -43,8 +44,8 @@ float determineShadow(int index, vec4 worldPos){
4344
}
4445

4546
void main() {
46-
float depth = texture2D(m_DepthTexture,texCoord).r;
47-
vec4 color = texture2D(m_Texture,texCoord);
47+
float depth = getDepth(m_DepthTexture,texCoord).r;
48+
vec4 color = getColor(m_Texture,texCoord);
4849

4950
//Discard shadow computation on the sky
5051
if(depth == 1.0){

jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.j3md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@ MaterialDef Post Shadow {
3131
Texture2D DepthTexture
3232

3333
Boolean BackfaceShadows //Not used.
34-
Int NumSamples //Not used.
34+
Int NumSamples
35+
Int NumSamplesDepth
3536
}
3637

3738
Technique {
@@ -47,6 +48,8 @@ MaterialDef Post Shadow {
4748
FILTER_MODE : FilterMode
4849
PCFEDGE : PCFEdge
4950
SHADOWMAP_SIZE : ShadowMapSize
51+
RESOLVE_MS : NumSamples
52+
RESOLVE_DEPTH_MS : NumSamplesDepth
5053
}
5154
}
5255
}

jme3-core/src/main/resources/Common/ShaderLib/MultiSample.glsllib

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ uniform int m_NumSamplesDepth;
1818
// NOTE: Only define multisample functions if multisample is available
1919
#if defined(GL_ARB_texture_multisample) || (defined GL_ES && __VERSION__>=310)
2020
vec4 textureFetch(in sampler2DMS tex,in vec2 texC, in int numSamples){
21-
ivec2 iTexC = ivec2(texC * vec2(textureSize(tex)));
22-
vec4 color = vec4(0.0);
23-
for (int i = 0; i < numSamples; i++){
24-
color += texelFetch(tex, iTexC, i);
25-
}
26-
return color / float(numSamples);
21+
ivec2 iTexC = ivec2(texC * vec2(textureSize(tex)));
22+
vec4 color = vec4(0.0);
23+
for (int i = 0; i < numSamples; i++){
24+
color += texelFetch(tex, iTexC, i);
25+
}
26+
return color / float(numSamples);
2727
}
2828

2929
vec4 fetchTextureSample(in sampler2DMS tex,in vec2 texC,in int sampleId){
@@ -40,11 +40,23 @@ vec4 getColorSingle(in sampler2DMS tex, in vec2 texC){
4040
return texelFetch(tex, iTexC, 0);
4141
}
4242

43-
vec4 getDepth(in sampler2DMS tex,in vec2 texC){
44-
return textureFetch(tex,texC,m_NumSamplesDepth);
43+
vec4 getDepth(in sampler2DMS tex, in vec2 texC){
44+
return textureFetch(tex,texC,m_NumSamplesDepth);
45+
4546
}
4647

47-
#endif
48+
vec4 getDepthMax(in sampler2DMS tex, in ivec2 coord, in int numSamples){
49+
vec4 result = vec4(0.0);
50+
for (int i = 0; i < numSamples; i++){
51+
result = max(result, texelFetch(tex, coord, i));
52+
}
53+
return result;
54+
}
55+
56+
ivec2 getTextureSize(in sampler2DMS tex) {
57+
return textureSize(tex);
58+
}
59+
#endif // Multisampling
4860

4961
vec4 fetchTextureSample(in sampler2D tex,in vec2 texC,in int sampleId){
5062
return texture2D(tex,texC);
@@ -55,10 +67,17 @@ vec4 getColor(in sampler2D tex, in vec2 texC){
5567
}
5668

5769
vec4 getColorSingle(in sampler2D tex, in vec2 texC){
58-
return texture2D(tex, texC);
70+
return texture2D(tex,texC);
5971
}
6072

61-
vec4 getDepth(in sampler2D tex,in vec2 texC){
73+
vec4 getDepth(in sampler2D tex, in vec2 texC){
6274
return texture2D(tex,texC);
6375
}
6476

77+
vec4 getDepthMax(in sampler2D tex, in ivec2 coord, in int numSamples){
78+
return texelFetch(tex, coord, 0);
79+
}
80+
81+
ivec2 getTextureSize(in sampler2D tex){
82+
return textureSize(tex, 0);
83+
}

jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ private ShaderDependencyNode loadNode(Reader reader, String nodeName) {
108108
}
109109
} else if (tln.startsWith("#extension ")) {
110110
sbExt.append(ln).append('\n');
111+
} else if (tln.startsWith("#version ")) {
112+
// #version must appear before the extensions, so treat it like one.
113+
sbExt.append(ln).append('\n');
111114
} else {
112115
sb.append(ln).append('\n');
113116
}

0 commit comments

Comments
 (0)