Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/command-line-slangc-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,11 @@ Negates (additively inverts) SV_Position.y before writing to stage output.
Reciprocates (multiplicatively inverts) SV_Position.w after reading from stage input. For use in fragment shaders only.


<a id="fgl-remap-z"></a>
### -fgl-remap-z
Remaps SV_Position.z from OpenGL clip space \[-w, w\] to standard \[0, w\] via z' = (z + w) / 2 before writing to stage output. GLSL target, vertex stage only.


<a id="fvk-use-entrypoint-name"></a>
### -fvk-use-entrypoint-name
Uses the entrypoint name from the source instead of 'main' in the spirv output.
Expand Down
3 changes: 3 additions & 0 deletions include/slang.h
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,9 @@ typedef uint32_t SlangSizeT;
// watchdog timeouts heavy coverage can trigger) at the cost of exact
// counts. Off by default.

GLSLRemapZ = 153, // bool: GLSL vertex output only. Remap SV_Position.z from OpenGL
// clip space [-w, w] to standard [0, w] via z' = (z + w) / 2.

CountOf,
};

Expand Down
1 change: 1 addition & 0 deletions source/slang/slang-compiler-options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ void CompilerOptionSet::writeCommandLineArgs(Session* globalSession, StringBuild
case CompilerOptionName::MatrixLayoutColumn:
case CompilerOptionName::VulkanInvertY:
case CompilerOptionName::VulkanUseDxPositionW:
case CompilerOptionName::GLSLRemapZ:
case CompilerOptionName::VulkanUseEntryPointName:
case CompilerOptionName::VulkanUseGLLayout:
case CompilerOptionName::VulkanEmitReflection:
Expand Down
18 changes: 18 additions & 0 deletions source/slang/slang-emit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2252,6 +2252,24 @@ Result linkAndOptimizeIR(
SLANG_PASS(invertYOfPositionOutput);
if (targetProgram->getOptionSet().getBoolOption(CompilerOptionName::VulkanUseDxPositionW))
SLANG_PASS(rcpWOfPositionInput);

// `-fgl-remap-z` is scoped to the textual GLSL target and the vertex stage only.
// The OpenGL [-1, 1] NDC depth convention only differs from the standard [0, 1] used by
// Vulkan/SPIR-V/D3D/Metal, so the remap must NOT run for SPIR-V (which also passes
// isKhronosTarget) -- hence the inner CodeGenTarget::GLSL check. remapZOfPositionOutput
// rewrites every position output in the linked module, so to stay strictly vertex-only it
// runs only when exactly one entry point is being generated and that entry point is a
// vertex shader. A mixed-stage whole-program request (e.g. vertex + mesh, which reaches
// this path with getEntryPointCount() > 1) is left untouched rather than risk remapping a
// non-vertex stage's position output, keeping the IR pass itself target/stage-agnostic.
if (target == CodeGenTarget::GLSL &&
targetProgram->getOptionSet().getBoolOption(CompilerOptionName::GLSLRemapZ) &&
codeGenContext->getEntryPointCount() == 1 &&
codeGenContext->getEntryPoint(codeGenContext->getSingleEntryPointIndex())->getStage() ==
Stage::Vertex)
{
SLANG_PASS(remapZOfPositionOutput);
}
}

BufferElementTypeLoweringOptions bufferElementTypeLoweringOptions = {};
Expand Down
75 changes: 75 additions & 0 deletions source/slang/slang-ir-vk-invert-y.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,81 @@ void invertYOfPositionOutput(IRModule* module)
}
}

// Return `originalVector` with its z (index 2) component remapped from the OpenGL clip-space
// depth range [-w, w] to the standard [0, w] range (NDC z in [-1, 1] -> [0, 1] after the
// perspective divide), computing z' = (z + w) / 2. Unlike `_invertYOfVector`, the map is
// affine and reads both z and w, so it must operate on the full position vector.
static IRInst* _remapZOfVector(IRBuilder& builder, IRInst* originalVector)
{
auto vectorType = as<IRVectorType>(originalVector->getDataType());
SLANG_ASSERT(vectorType);
auto elementType = vectorType->getElementType();
UInt elementIndexZ = 2;
UInt elementIndexW = 3;
auto originalZ = builder.emitSwizzle(elementType, originalVector, 1, &elementIndexZ);
auto originalW = builder.emitSwizzle(elementType, originalVector, 1, &elementIndexW);
auto sum = builder.emitAdd(elementType, originalZ, originalW);
auto remappedZ = builder.emitDiv(elementType, sum, builder.getFloatValue(elementType, 2.0));
auto newVal = builder.emitSwizzleSet(
originalVector->getDataType(),
originalVector,
remappedZ,
1,
&elementIndexZ);
return newVal;
}

// Find outputs to SV_Position and remap their clip-space z right before the write, so a shader
// authored against the OpenGL [-1, 1] NDC depth convention emits the standard [0, 1] depth. This
// reuses `invertYOfPositionOutput`'s store/getElementPtr traversal but transforms only the value
// written to the position output; unlike `invertYOfPositionOutput` it intentionally does NOT fix
// up `IRLoad` uses of the global. invert-y handles loads so that a shader which reads its negated
// `y` back observes the value it wrote; the analogous concern here is that a read-back would
// observe the remapped `z`. Omitting the load branch is therefore correct only under the invariant
// that a gated GLSL vertex `gl_Position` is not read back within the shader after the output write
// (the ordinary case: the position is computed, written, and not re-read). If a future IR shape
// routed a post-write `gl_Position` load through this pass, a load fix-up mirroring invert-y would
// be required -- and note `_remapZOfVector` could not simply be reused for it, since z' = (z + w) /
// 2 is not its own inverse (that non-involutivity explains the helper-reuse limit, not the
// omission's correctness). The position output is lowered as a single full-`float4` store, so the
// store value is always a vector and `_remapZOfVector` asserts that invariant rather than this pass
// guarding against it. The pass is scheduled exclusively for the GLSL target on vertex entry points
// (see linkAndOptimizeIR in slang-emit.cpp), so it performs no target/stage checks of its own.
void remapZOfPositionOutput(IRModule* module)
{
for (auto globalInst : module->getGlobalInsts())
{
if (globalInst->findDecoration<IRGLPositionOutputDecoration>())
{
// Find all stores to it (including those reached through a getElementPtr).
IRBuilder builder(module);
List<IRUse*> useWorkList;
auto processUse = [&](IRUse* use)
{
if (auto store = as<IRStore>(use->getUser()))
{
if (getRootAddr(store->getPtr()) != globalInst)
return;

builder.setInsertBefore(store);
auto originalVal = store->getVal();
auto remappedVal = _remapZOfVector(builder, originalVal);
builder.replaceOperand(&store->val, remappedVal);
}
else if (auto getElementPtr = as<IRGetElementPtr>(use->getUser()))
{
traverseUses(getElementPtr, [&](IRUse* use) { useWorkList.add(use); });
}
};
traverseUses(globalInst, processUse);
for (Index i = 0; i < useWorkList.getCount(); i++)
{
processUse(useWorkList[i]);
}
}
}
}


static IRInst* _invertWOfVector(IRBuilder& builder, IRInst* originalVector)
{
Expand Down
1 change: 1 addition & 0 deletions source/slang/slang-ir-vk-invert-y.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Slang
struct IRModule;
void invertYOfPositionOutput(IRModule* module);
void rcpWOfPositionInput(IRModule* module);
void remapZOfPositionOutput(IRModule* module);
} // namespace Slang

#endif
6 changes: 6 additions & 0 deletions source/slang/slang-options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -876,6 +876,11 @@ void initCommandOptions(CommandOptions& options)
nullptr,
"Reciprocates (multiplicatively inverts) SV_Position.w after reading from stage input. "
"For use in fragment shaders only."},
{OptionKind::GLSLRemapZ,
"-fgl-remap-z",
nullptr,
"Remaps SV_Position.z from OpenGL clip space [-w, w] to standard [0, w] via "
"z' = (z + w) / 2 before writing to stage output. GLSL target, vertex stage only."},
{OptionKind::VulkanUseEntryPointName,
"-fvk-use-entrypoint-name",
nullptr,
Expand Down Expand Up @@ -2660,6 +2665,7 @@ SlangResult OptionsParser::_parse(int argc, char const* const* argv)
case OptionKind::DumpIr:
case OptionKind::VulkanInvertY:
case OptionKind::VulkanUseDxPositionW:
case OptionKind::GLSLRemapZ:
case OptionKind::VulkanUseEntryPointName:
case OptionKind::VulkanUseGLLayout:
case OptionKind::VulkanEmitReflection:
Expand Down
41 changes: 41 additions & 0 deletions tests/cross-compile/gl-remap-z.slang
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// gl-remap-z.slang

// Test that '-fgl-remap-z' remaps SV_Position.z from the OpenGL clip-space depth range
// [-w, w] to the standard [0, w] range (z' = (z + w) / 2) on the GLSL vertex output, and
// that it composes with '-fvk-invert-y' (the two transforms touch independent components).

//TEST:SIMPLE(filecheck=CHECK):-target glsl -entry main -stage vertex -profile vs_5_1 -fgl-remap-z
//TEST:SIMPLE(filecheck=COMPOSE):-target glsl -entry main -stage vertex -profile vs_5_1 -fvk-invert-y -fgl-remap-z
//TEST:SIMPLE(filecheck=NOREMAP):-target glsl -entry main -stage vertex -profile vs_5_1

struct VOutput
{
float4 v : SV_Position;
}


VOutput main()
{
VOutput output;
output.v = float4(1, 2, 3, 4);

// The z component of the written position is set from an expression that reads both its
// z and its w and halves the sum (i.e. z' = (z + w) / 2), then the result is written to
// gl_Position.
// CHECK: [[TMP:[_A-Za-z0-9]+]].z = {{.*}}output_0.v_0.z{{.*}}output_0.v_0.w{{.*}}/{{.*}}2.0{{.*}};
// CHECK: gl_Position = [[TMP]];

// Composed with -fvk-invert-y: y is additively inverted AND z is remapped reading both
// z and w and halving. The two transforms are independent, so order does not matter
// (CHECK-DAG).
// COMPOSE-DAG: {{[_A-Za-z0-9]+}}.y = - output_0.v_0.y;
// COMPOSE-DAG: {{[_A-Za-z0-9]+}}.z = {{.*}}.z{{.*}}.w{{.*}}/{{.*}}2.0{{.*}};
// COMPOSE: gl_Position =

// Off by default: without '-fgl-remap-z' the position is written unmodified, with no
// (z + w) / 2 temp, confirming the remap is a pure opt-in (and that the gate, not the
// shader, controls it).
// NOREMAP-NOT: / 2.0
// NOREMAP: gl_Position = output_0.v_0;
return output;
}
Loading